home | O'Reilly's CD bookshelfs | FreeBSD | Linux | Cisco | Cisco Exam  


Learning Perl

Learning PerlSearch this book
Previous: 15.6 Exercises Chapter 16 Next: 16.2 Packing and Unpacking Binary Data
 

16. System Database Access

16.1 Getting Password and Group Information

The information that the UNIX system keeps about your username and user ID is fairly public. In fact, nearly everything but your unencrypted password is available for perusal by any program that cares to scan the /etc/passwd file. This file has a particular format, defined in passwd (5), which looks something like this:

name:passwd:uid:gid:gcos:dir:shell

The fields are defined as follows:

name

The login name of the user

passwd

The encrypted password, or something simple if a shadow password file is being used

uid

The user ID number (0 for root , nonzero for normal users)

gid

The default login group (group 0 may be privileged, but not necessarily)

gcos

The GCOS field, which typically contains the user's full name followed by a comma and some other information

dir

The home directory (where you go when you type cd without any arguments and where most of your "dot-files" are kept)

shell

Your login shell, typically /bin/sh or /bin/csh (or maybe even /usr/bin/perl , if you're crazy)

A typical portion of the password file looks like this:

fred:*:123:15:Fred Flintstone,,,:/home/fred:/bin/csh
barney:*:125:15:Barney Rubble,,,:/home/barney:/bin/csh

Now, Perl has enough tools to parse this kind of line easily (using split , for example), without drawing on special purpose routines. But the UNIX programing library does have a set of special routines: getpwent (3), getpwuid (3), getpwnam (3), and so on. These routines are available in Perl using the same names and similar arguments and return values.

For example, the getpwnam routine becomes the Perl getpwnam function. The single argument is a username (like fred or barney ), and the return value is the /etc/passwd line split apart into a list with the following values:

($name, $passwd, $uid, $gid, $quota, $comment, 
 $gcos, $dir, $shell)

Note that there are few more values here than in the password file. For every UNIX system we've seen, the $quota field is always empty, and the $comment and the $gcos field often both contain the entire GCOS field. So, for good old fred , you get

("fred", "*", 123, 15, "", "Fred Flintstone,,,",
 "Fred Flintstone,,,", "/home/fred"," /bin/csh")

by invoking either of the following two calls:

getpwuid(123)
getpwnam("fred")

Note that getpwuid takes a UID number, while getpwnam takes the login name as its argument.

The getpwnam and getpwuid functions also have a return value when called in a scalar sense. They each return the thing you've asked them to get. For example:

$idnum = getpwuid("daemon");
$login = getpwnam(25);

You'll probably want to pick this apart, using some of the list operations that we've seen before. One way is to grab a part of the list using a list slice, such as getting just the home directory for Fred using:

($fred_home) = (getpwnam ("fred"))[7]; # put Fred's home

How would you scan through the entire password file? Well, you could do something like this:

for($id = 0; $id <= 10_000; $id++)     {
    @stuff = getpwuid $id;
} ### not recommended!

But this is probably the wrong way to go. Just because there's more than one way to do it doesn't mean that all ways are equally cool.

You can think of the getpwuid and getpwnam functions as random access ; they grab a specific entry by key, so you have to have a key to start with. Another way of accessing the password file is sequential access  - grabbing each entry one at a time.

The sequential access routines for the password file are the setpwent , getpwent , and endpwent functions. Together, these three functions perform a sequential pass over all values in the password file. The setpwent function initializes the scan at the beginning. After initialization, each call to getpwent returns the next entry from the password file. When there is no more data to process, getpwent returns an empty list. Finally, calling endpwent frees the resources used by the scanner; this is performed automatically upon exiting the program as well.

This description begs for an example, so here's one now:

setpwent();                                  # initialize the scan
while (@list = getpwent()) {                 # fetch the next entry
    ($login,$home) = 

@list[0,7];             # grab login name and home
    print "Home directory for $login is $home\n"; # say so
}
endpwent();                                  # all done

This example shows the home directory of everyone in the password file. Suppose you wanted them alphabetically by home directory? We learned about sort in the previous chapter, so let's use it:

setpwent();                      # initialize the scan
while (@list = getpwent()) {     # fetch the next entry
    ($login,$home) = @list[0,7]; # grab login name and home
    $home{$login} = $home;       # save it away
}
endpwent();                      # all done
@keys = sort { $home{$a} cmp $home{$b} } keys %home;
foreach $login (@keys) {         # step through the sorted names
    print "home of $login is $home{$login}\n";
}

This fragment, while a little longer, illustrates an important thing about scanning sequentially through the password file; you can save away the pertinent portions of the data in data structures of your choice. The first part of the example scans through the entire password file, creating a hash where the key is the login name and the value is the corresponding home directory for that login name. The sort line takes the keys of the hash and sorts them according to string value. The final loop steps through the sorted keys, printing each value in turn.

Generally, you should use the random access routines ( getpwuid and getpwnam ) when you are looking up just a few values. For more than a few values, or even an exhaustive search, it's generally easier to do a sequential access pass (using setpwent , getpwent , and endpwent ) and extract the particular values you'll be looking for into a hash.[ 1 ]

[1] If you're on a site with a large NIS map, you probably do not want to preprocess the password file this way for performance reasons.

The /etc/group file is accessed in a similar way. Sequential access is provided with the setgrent , getgrent , and endgrent calls. The getgrent call returns values of the form:

($name, $passwd, $gid, $members)

These four values correspond roughly to the four fields of the /etc/group file, so see the descriptions in the manpages about this file format for details. The corresponding random access functions are getgrgid (by group ID) and getgrnam (by group name).