[Chapter 16] System Database Access
Author: Randal Schwartz, Tom Christiansen & Larry Wall
Date: 1999-07-02T01:35:33Z

#

| Chapter 16 |

# 16. System Database Access

Contents:
Getting Password and Group Information
Packing and Unpacking Binary Data
Getting Network Information
Exercise

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

15.6 Exercises |  | 16.2 Packing and Unpacking Binary Data