CS 3210 - Lab 16 - Names and the User Database

Last lab, gang. Today we're going to see how to get information about user/group names and IDs.

This should be helpful to anyone who is writing an implementation of who or finger.

The chapter correponding to this lab is Chapter 26.

Getting User Information

Getting information about users on a Unix system is easy: you just read the info out of the world-readable system password file, /etc/passwd. You can have a look at the kind of stuff that's in there by typing less /etc/passwd. (You can also type man 5 passwd to get documentation on the format of the /etc/passwd file.)

Group information is stored in the file /etc/group.

Historical Note on the /etc/passwd file

The concerned student may be troubled to hear that the system password file is world-readable. After all, don't they store passwords in there?

Short Answer: They used to, but they don't any more. They just store user information in there now.

Longer Answer:

To handle logins from multiple users, Unix systems run a program called getty on each device (console or port) where a tty could be attached. (Type ps fax | grep getty -- "man getty") The getty program waits until a terminal is detected and then execs the login program, which performs user authentication. The login program ("man login") prompts for a username and password and checks that information against entries in the /etc/passwd file.

Originally the /etc/passwd file just stored usernames, user IDs and passwords, but since this file is read every time a user logs in, it became convenient to store additional login information such as: home directory, shell, group name, and so forth. Additionally, people decided to store additional information, like the full name, office number, phone numbers, etc. This additional information is often referred to as "GECOS". Numerous programs (such as chfn) could now use this file to display or change GECOS information about users.

Note: The term "GECOS" originally stood for "General Electric Comprehensive Operating Supervisor". See this page for an explanation of this term and numerous other cryptic Unix names.

It didn't take long before some malicious users decided they could use the /etc/passwd file to try to crack passwords by using programs like crack. Compounding this problem was the fact that so many programs were now dependent on having this file world-readable to be able to access user information. What to do?

Eventually, folks decided to leave the user information in /etc/passwd but move the password entries to a file called /etc/shadow, so called "shadow passwords". (Notice how the password fields all have little 'x's in them.) It probably would be better if the 'passwd' file had been called something like "userstuff" and the 'shadow' file had been called "passwd", but such is the way of progress.

Getting / setting passwd info in the shell

id - Prints the user ID and group ID(s) for the current user or the given user (try typing id root).

passwd - Used to change your current password. Can also be used by root to change the password for another user. We did this the first night of class.

finger - Displays info about a user. Format is finger username@host. To get information about a user on the local host, you can just type finger username. To talk to the finger daemon directly, type nc localhost 79 and type these commands: "help", "username" (NOT "search username") and "userlist-only". Note also that you can create a .plan file in your home directory and the contents will be displayed by finger (vi ~/.plan).

chfn - Changes the "gecos" information that is displayed by the finger program.

groups - Lists the groups that you or another user belong to.

adduser - Adds a new user to the system or adds a user to a group. (Type "adduser --help".)

Getting passwd info in a C program

To get a specific password file entry, you can call getpwnam() to retrieve a specific username that matches the string you pass. Alternatively, you can call getpwuid() to retrieve it by a user ID you pass. See p.464.

Additionally, you can retrieve group information by calling getgrnam() and getgrid() which work like their getpw* counterparts. See p.465.

Example program: pw-grp.c - uses the above calls to retrieve information about a specific user. You can pass it either a username or ID.

Example program: id.c - A clone of the id program. From your book on p.466.

If you want to iterate through all the entries in the passwd file, first you call setpwent() to "rewind" (or initialize) the password file. Next, you can call getpwent() over and over to retrieve the current entry and advance to the next entry. Lastly, you can call endpwent() when you're done to close the passwd file.

Example program: pwiter.c

Getting User Login information

You can also retrieve login information about a currently logged-in user. It is stored in the /var/run/utmp, /var/log/wtmp, and occasionally /var/log/btmp. Note that unlike the passwd and group files, this information is not stored in human-readable text, but in a binary format (which is kind of unusual on a Unix system).

In the Shell

who - Shows who is currently logged in. The man page shows numerous options that it supports.

w - Like who, but shows the name of the the current process being executed by the user.

last - Shows the users who last logged in. The related program lastb shows failed login attemts. This information could be used by a sysadmin to audit a system.

In a C Program

You can iterate through all the entries in the utmp file by calling setutent() to "rewind" the file, then getutent() repeatedly to retrieve entries, and last endutent() to close the file.

utmpiter.c - Iterates through all the entries in utmp. The first column shows the "type" of entry.

You can search for specific entries in utmp by calling getutid() or getutline(). You can also write entries into utmp by calling pututline().

utmp-man.c - From the man page for getutent. Note that this program must be run as root because it makes changes to the system file /var/run/utmp. I will demo it in class.

We will not concern ourselves today with dealing with the /var/log/wtmp file.

Further Study

The man pages for passwd(5), group(5) and utmp(5), as well as the various library functions mentioned previously.

How do I check a user's password? from the UNIX programming FAQ

Your book, Chapter 26.

Lab 16

For this (last!) lab, you guys will write a program that prints various and sundry information about the user based on the following command-line parameters:

usage: userinfo [options] username
  -u  show user ID
  -g  show group ID
  -i  show gecos information
  -h  show home directory
  -s  show shell
  -G  show group name
the following login info options are also available
  -p  show login pid
  -d  show device of tty
  -I  show init ID
  -H  show hostname of remote login
  -t  show time of login

Some Steps to Help You

  1. Put the usage message above in a seperate function called usage(). If an invalid option was given or a username or ID was not provided, you will call this function to print the usage message.
  2. Use getopt() to process the various options. (You do not have to use popt or getopt_long unless you really want to.)
  3. Try to retrieve a password entry for the user either by name (getpwnam()) or ID (getpwuid()).
  4. Depending on which command-line options were passed, print the various information from the password entry (struct passwd).
  5. If any of the login information options were given, open up the utmp file (setutent()) and iterate through it (getutent()). If the utmp entry matches the username specified (hint: use strcmp()), print the various fields from the utmp entry (struct utmp). When you're done iterating, close the utmp file (endutent()).

Save this program in a file called userinfo.c. The whole file should be about 125 (!) lines long, but you will be able to cut-n-paste a bunch of stuff, and the option processing is largely repetitious. Put your source code and Makefile in a lab16/ directory.

Some hints on option processing

First of all, have a look at the Lab 14 notes. For this exercise, the best way to handle the options is to just set a bunch of integer variables like so:

	int show_uid = 0;
	int show_gid = 0;
	...

Then, your option processing loop, you'll "turn on" the options like so:

	while ((opt = getopt(argc, argv, "ugihsGpdIHt?")) > 0) {
		switch (opt) {
			case 'u': show_uid = 1; break;
			case 'g': show_gid = 1; break;
			...
			case '?':
			default:
				usage();
		}
	}

	/* make sure they entered a username */
	if (optind == argc) usage();

Later on in your code you can then just say:

	if (show_uid) printf("user ID: %d\n", pw->pw_uid);
	if (show_gid) printf("group ID: %d\n", pw->pw_gid);
	...

Output

Here's some examples of the program in use:

$ ./userinfo -u
usage: userinfo [options] username
  -u  show user ID
  -g  show group ID
  -i  show gecos information
  -h  show home directory
  -s  show shell
  -G  show group name
the following login info options are also available
  -p  show login pid
  -d  show device of tty
  -I  show init ID
  -H  show hostname of remote login
  -t  show time of login

$ ./userinfo -uihs markw
user name: markw
user ID: 1002
gecos information: Mark Whitley,Lab 403,none,555-1297
home directory: /home/markw
shell: /bin/bash

$ ./userinfo -pdIt 1002
user name: markw
Login information:
10741  vt00markw  pts/0  Tue Jul  8 13:12:27 2003  
16965  vt01markw  pts/1  Thu Jul 10 14:11:43 2003  
1662  vt02markw  pts/2  Thu Jul 17 13:15:53 2003  
10557  vt03markw  pts/3  Tue Jul  8 12:38:37 2003  
17272  vt04markw  pts/4  Thu Jul 10 14:21:15 2003  
28597  vt05markw  pts/5  Tue Jul 15 11:09:15 2003  
19931  vt06markw  pts/6  Fri Jul 11 23:33:31 2003  
25795  vt07markw  pts/7  Mon Jul 14 23:11:41 2003  
25950  vt08markw  pts/8  Thu Jul 17 13:15:49 2003  
29571  vt09markw  pts/9  Tue Jul 15 12:52:23 2003  

$ ./userinfo -uihs -pdIt markw
user name: markw
user ID: 1002
gecos information: Mark Whitley,Lab 403,none,555-1297
home directory: /home/markw
shell: /bin/bash
Login information:
10741  vt00markw  pts/0  Tue Jul  8 13:12:27 2003  
16965  vt01markw  pts/1  Thu Jul 10 14:11:43 2003  
1662  vt02markw  pts/2  Thu Jul 17 13:15:53 2003  
10557  vt03markw  pts/3  Tue Jul  8 12:38:37 2003  
17272  vt04markw  pts/4  Thu Jul 10 14:21:15 2003  
28597  vt05markw  pts/5  Tue Jul 15 11:09:15 2003  
19931  vt06markw  pts/6  Fri Jul 11 23:33:31 2003  
25795  vt07markw  pts/7  Mon Jul 14 23:11:41 2003  
25950  vt08markw  pts/8  Thu Jul 17 13:15:49 2003  
29571  vt09markw  pts/9  Tue Jul 15 12:52:23 2003  

If you can run the same tests with your program and get the same basic output, you probably did it right.

Files

Here is a list of the files created in this lab.

Please turn in just the userinfo.c file with your name, lab # and class # at the top.