Leor Zolman bought his first microcomputer (an IMSAI 8080) while in high school in L.A., carried it to M.I.T., withdrew, and wrote the BDS C compiler with it in assembly language. That was enough assembly language hacking to last a lifetime, so now he enjoys UNIX/Xenix system administration, article writing, and raising his newborn daughter Katelyn. You can reach him at leor@rdpub. com or uunet!bdsoft!rdpub!leor.
Generic DOS is a single-user, single-process operating system. Only one person can directly use a particular client (as opposed to network server) DOS system interactively at a time, and that user may only run one program at a time.
If a DOS system is used by more than one person, users will quickly discover that there are no built-in file management mechanisms for associating individual files with particular users. The users must keep track of their own files (if they understand the DOS directory structure well enough), or all the software applications must manage file ownership, perhaps by prompting users for their identity and then associating users with their own uniquely-named directories or maintaining ownership data in special system files.
Logging In
In contrast, systems designed to support multiple users (whether simultaneously via multi-processing or not), such as the UNIX family of operating systems, maintain file ownership information for every program and data file on the system. These systems always require a user to log in at the beginning of any session. When logging in, a user must provide a public user id along with an optional, secret password before he can use the system. The user id is typically an alphanumeric string derived from the user's name (although some systems, such as CompuServe, use a numeric format). The password is a text string known only to the user and protected by the system.Once a user has given an acceptable login id and password, the system sets some globally-accessible variables (so that other software on the system can identify the user) and executes an initial command sequence designed exclusively for that user. (On UNIX systems, the login program generally starts up one of several available command processors, and that command processor becomes responsible for executing customized startup commands.)
I wrote the program presented in this article to make some rudimentary multi-user (although not multi-processing) system features available under DOS. The program is named login, for the UNIX facility after which it has been patterned. As with the UNIX version, a password file containing information about each legal user must be present somewhere on the system. Password files typically contains one physical ASCII line of information for each user, including at least the user's id and optional password. My version supports only these two fields, while the UNIX version contains other fields for controlling features not supported by DOS.
This DOS login offers at least two useful features, even on a single-user (at a time) system: security and simplified user interface.
Login is a start toward preventing unauthorized use of a machine, although there probably aren't any 100 percent-reliable security measures for DOS. (In fact, my friend Jay Sage of ZCPR3 fame claims perhaps justifiably that the ZCPR3 system an eight-bit derivative of CP/M is inherently much more secure an operating system than DOS could ever become.)
Probably the more practical benefit of login is the ability to simplify the user interface for users who are "DOS illiterate." The user doesn't need to know about directories and command prompts. All he needs to remember is his login id, password, and how to use the applications his customized startup sequence launches for him.
What the User Sees
From the user's point of view, my login, as well as UNIX's, behaves in the following manner. When invoked, login prints the string
login:and waits for the user to enter a login id. When the user presses return, login checks to see if the user entered a valid id. If the login-id is correct, and the password file contains no password associated with that user, then the login is approved. (Obviously, security can't be an issue under such a configuration.) If the login id requires a password, or even if the login id was invalid, login first displays
password:then turns off keyboard echoing and waits for the user to enter a password. By prompting for a password even when the login id was invalid, login doesn't yield information concerning valid login ids.After the user enters a password and presses the return key, login checks that the password is correct for the given user id. If so, then the login is approved. If it isn't, or if the user id was unrecognized, login says
login incorrect.and repeats the whole process with
login:This pattern continues until a login is approved. You can exit the program only by logging in correctly or rebooting the machine. If the machine has a bootable floppy drive, then you can bypass login by inserting a floppy boot disk (that didn't have login as part of the startup in AUTOEXEC. BAT) and rebooting so much for bullet-proof security. By configuring the floppy as B: on some systems, however, or if used on systems without any floppies, even most DOS-literate users would be compelled to play the login game.
Starting Up the User
Once the user enters the correct login sequence, login performs two more tasks to get the session underway.First login writes the user's id string into the special file CURRENT_USER in the STARTUP_DIR directory (these names are all #defined constants you may modify in the source code.) Applications can read the file and determine the current user.
Second, login looks for a "startup file" named USER.BAT where USER is the user's login id, in the STARTUP_DIR directory. If found, the file is passed to COMMAND.COM (DOS's command processor) for execution via the system() library function. This startup file would be the place to put the name of the command(s) you want executed when that user logs in; the command might be an individual application, a menu system, or some other kind of program.
To maintain security, the last line of the user's startup file should specify the login command itself, to close the loop.
For another blow to the security issue, remember that most major applications have commands that allow "shell escapes" to DOS...and that most applications in general can be brought down hard (i.e., crashed) without too much effort, presenting the user with an invitation from DOS to press Control-C to abort the current batch file and return to a system prompt. Sigh.
How It Works
For console I/O, login contains several functions that talk directly to the low-level console interface routines available via DOS's BDOS to disable the interrupt keys Control-C / Control-Break.zgetch() reads a character from the keyboard, mapping carriage returns to newline characters for convenience. zgets() reads an entire line from the keyboard into a character buffer provided as a parameter, using zgetch() to read characters. The parameter echo to zgets() controls whether or not the keystrokes are echoed back to the screen as they are typed. The format of the line collected up by zgets() is the same as for the standard gets() function, i.e., null-terminated with no newline character at the end.
zputs() writes a line to the screen. The library function putch() is used to write each character, and newlines are expanded to CR-LF sequences.
The main program begins by reading in all information from the password file into the array of structures named users. As each line is read in from the file using fscanf(), the name and passwd elements of users are assigned the first and second strings on the line, respectively. If at least one string is not found on a line, the program assumes the end of file has been reached (see line 67). After each line has been loaded successfully, the loop counter variable nusers is incremented.
If debugging is enabled, lines 69-73 display all the password information as it is read from the file. To enable debugging, insert the line
#define DEBUG 1at the top of the source file.Once all password information has been loaded (and lines 75-76 have verified that at least one valid user name is present), the password file is closed and the main loop is started. At this point, the variable nusers tells you how many records were found in the password file.
Lines 82 and 83 prompt for a user id and read in a line of text from the console into the character buffer named linbuf, with keystroke echo enabled. Lines 85-87 loop through the list and compare the strings to see if the entered string matches any of the known user names. A match forces an immediate break out of the loop, leaving the counter variable i equal to the index into users of the matching user id (between 0 and nusers -- 1.) If no matching id was found, i ends up with the value of nusers.
Lines 90-91 handle the case in which no password is required by skipping over the password checking code. Lines 90-91 skip the password checking code if and only if the following two conditions are met:
Otherwise, password checking springs into action. The password prompt is displayed and a line of text is read from the keyboard again, but this time with the echo turned off (line 94.)
- a valid user id was entered (i != nusers), and
- the password for that user is a null string (!*users [i].passwd).
<Another two conditions must be met for the entered password to be accepted as valid:
Unless both conditions are met, the incorrect login message is displayed and the main loop keeps repeating.
- the user id supplied above must be recognized (i != nusers), and
- the entered string must match the password from the password file.
Upon Success
We reach line 101 after a correct login sequence. Login prints a few blank lines for the sake of aesthetics, then lines 103-110 attempt to create the CURRENT_USER file and write out to it the name of the user who just logged in.Finally, lines 112-122 construct the name of the appropriate batch file to run for the user. The name is created by starting with the name of the startup directory, appending the user id, and then appending the .BAT extension onto the end. Login opens the file to see if it is present; if so, the file is closed and the filename is passed to the system() function for execution.
Logging Out
This program is only a starting point. To use the facility most effectively, you must still be able to read information from the CURRENT_USER file into your applications. Whether used to its capacity or not, secured or otherwise, there is still some value in being able to customize each user's initial environment to suit his or her needs in the most user-friendly way.
Exercise
As written, login writes a file (specified by CURRENT_USER) containing the name of the active user. Another way to identify the current user to the system is to set an environment variable, say CURUSER, to the user's id. This method would be faster to execute, but you would then need the appropriate tools to access the environment information from the application programs. Using the Master Environment Package (from CUJ 11/89), modify login to set an environment variable instead of (or in addition to) writing a file to disk.
Listing 1