Cover V04, I04
Listing 1
Listing 2


closeit: A Login Management Tool

Tom Everson

Most system administrators work hard at keeping their systems up and available to users as much as possible. But there are times when you need to do just the opposite -- keep certain users off the system. I help support a UNIX system where I occasionally need to bar users from logging in for a short time while I make changes to their applications or profiles. I developed a quick script to let me do this. I found myself using the script often and eventually expanded it into a more finished utility.

It's easy enough to disable all logins on a UNIX system or lock any individual account, but I needed more. I wanted to be able to disable all accounts, just one account, or any list of accounts. And I needed to be able to reopen them quickly and selectively. Also, while I wanted to keep the users out, it was essential that I still be able to log in as those users or su to their accounts, since certain things could only be tested with their identities. Finally, the system is a commercial one, and the users pay for their access. They accept the need for occasional, and sometimes unexpected, maintenance, but appreciate knowing when they can expect to get access again. To preserve good customer relations I needed a way to tell them.

I call my script closeit, and it meets all of the above requirements. It comes in two parts. The first and largest (see Listing 1) is comprised of routines that let you identify the accounts you want to disable, compose your message to the user, and flag the accounts as closed. The second part (see Listing 2) is an addition to the system's /etc/profile file, which is sourced by the login shell for every user login. This part detects the flagged condition of the account, displays the message to the user, and aborts the login. It does not kill an active user session or abort current user processes; it simply prevents future logins until you reopen the account.

Script Functions

I wrote the script in the Korn Shell for a System V UNIX, but it could be implemented, with minor changes, in any UNIX environment. The main script begins with some variable assignments, proceeds to function definitions and then to the main line of its processing. Taken in order of appearance, the separate functions do the following.

Usage() uses a fairly standard format for informing the user of the purpose of the script and its proper syntax.

Getusers() looks at the /etc/passwd file and picks out the lines that end in sh, i.e., the "real" login accounts. It sets the fields of each successive line to the shell's positional parameters, and from these selects the user login name and home directory. It puts these in a file ($DIRECLIST), with each line of the form

username  home_directory

I explicitly exclude root and an account called sysnews. You may have special exclusions you want to make at your site. If you prefer, you can make this user list into a static file outside the script, but I want to know it's up to date, so I build it each time the script runs. Doing this also reduces the chance that it will become corrupted or "inadvertently" modified.

MakeMessage() lets you compose the message the debarred user will see. I almost always use the same format, which tells the user when the account can be expected to reopen. This default message is provided in the script. However, you can edit this into a new message with your favorite editor ($C_EDITOR).

WriteMessage() writes your message to a file called .closelogin (or a name of your choice) in the user's home directory; the file serves both to contain the message and to flag the account as closed. Before writing it it bundles the text into a here document using the shell's "<<" redirection and passes this to a subshell (lines 177-178). This has the effect of causing any shell variables that you include in the message to be evaluated. In my default message I use the variables $USERNAME and $OPENTIME. You can edit these or add others of your own. Once again, I create my message in the script, rather than storing it in an external file. Then I don't worry that it will become corrupted or be modified by sporting types who might get access to it (at least I don't worry as much).

ListWho() lists all the accounts that are closed at the time you invoke it. This is a standalone function and is also tacked on to other operations as additional confirmation of what you have done.

Finally, GoAhead() is a routine that gives the user a chance to back out. If the user continues, the routine checks to see if the $OPENTIME variable is being used in the user message and, if so, prompts for the reopening time.

The Main Program

The main line of programming is divided into two parts, depending on the name it is invoked under, which can be "closeit" or "openit." The closeit section validates the syntax of the command and uses the Usage() routine if it needs to. It then calls Getuser() and MakeMessage() and gives you a chance to back out. If your command was "closeit all," it then closes each account identified by GetUsers(). If you just entered "closeit," it will take user names one at a time from standard input, confirming each closure or giving you a failure message. Press your EOF key (usually Ctrl-D) when you are done.

You can also invoke the script as "openit." This line of the program lets you reopen all accounts or a list of accounts that you enter at standard input. It reports the results for each one: either the account reopened successfully, failed to do so, or wasn't closed in the first place. It will take input from standard input unless you invoke it as "openit all." In that case the exec < $DIRECLIST on line 285 will redirect input from the $DIRECLIST file and process each user account in turn.

When you give account names to this script, it will act on the first name in $DIRECLIST whose leading characters match what you give it. You don't need to enter the whole account name, except to avoid ambiguity; it will report back the actual account name that it is acting on.

Setting Up /etc/profile

The portion of this system that goes in /etc/profile is in Listing 2. This should be at the head of your /etc/profile file. It looks at the home directory of the user logging in to see if .closelogin exists there. If it does, it displays the message and waits for user input. Your message should request that the user hit the Enter key. When input is received, the routine looks to see if what was entered matches a special string, which it gets from another file. If it does, login proceeds normally. Otherwise a no-op program (/bin/false) is exec'd by the shell, aborting the user's login.

System Administration Considerations

Using a password in closeit lets you log in to the account even when the user can't. Note, however, that each user must be able to read /etc/profile; otherwise, /etc/profile is not used at login, and your efforts here will have no effect. If /etc/profile is readable by users, however, they will know where you store your secret password (the file /nowrite/.secret in my example), and this file must also be readable by them. In my case this is not a problem, since users do not have access to a shell. If your users do, they could easily find out what the password is and circumvent the system. You may want to make this less likely by writing a new password to the file just before using closeit. You could add this function to the script if you wish. The file that keeps the password is readable by all, but should be writable only by root.

If you want more protection you can consider using crypt or some other encryption mechanism to make the procedure more like a real password. But keep in mind that closeit is not a security system, and you shouldn't try to make it one. Don't use anyone's real password with closeit, especially not root's. It will not be secure. A clever user could devise other ways to circumvent the system, such as starting a daemon to clear the flag file from his home directory every few minutes. You could change the name of that file from time to time, but if the user is that uncooperative, look for other methods to control access.

The message you display to the user should also be created in a directory writable only by root and should itself be writable only by root. This will help ensure that no one except you can modify or delete it. closeit should only be executable by root; it tests for this at the head of the script.

When you use closeit, be sure you know how the account will be opened again if you should be unable to reopen it or forget to do so. Before using closeit you can pass

openit all >&-

to the UNIX at command, to ensure that everything gets opened up in, say, an hour, even if you aren't there to do it. And, of course, be sure that closeit can never close root's account. If at some point you can't use openit for any reason, use the find command to find any flag files in users' home directories and remove them.

To use closeit, put it somewhere in root's path and link it to openit in the same directory. Make sure only root can read, write, or execute it. Make changes to the variables at the head of the script if you need to, and set up your "password" file in the same root-writable-only directory where you will create your message. Test closeit first on special accounts you set up for that purpose. Put it into production only when you are satisfied that it works as you expect it to.

About the Author

Tom Everson is a partner in Nova Business Systems, Inc., a software developer and UNIX services company in Portland, Oregon. He got his start in computing with IBM in 1973 (when a word-processing program required a cruiser class mainframe), after receiving a PhD in Philosophy from the Johns Hopkins University. He has worked with PC-based UNIX systems for the last five years.