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