Linux Application Development (paperback) (2nd Edition)

   

28.1. ID-to-Name Translation

When you type ls -l to list the contents of the current directory, the third and fourth columns give the user ID and group ID that owns each file. It looks like this:

drwxrwxr-x 5 christid christid 1024 Aug 15 02:30 christid drwxr-xr-x 73 johnsonm root 4096 Jan 18 12:48 johnsonm drwxr-xr-x 25 kim root 2048 Jan 12 21:13 kim drwxrwsr-x 2 tytso tytso 1024 Jan 30 1996 tytso

But the kernel does not store the string christid anywhere; the ls program is translating from kernel-supplied numbers to names. It gets numbers from the stat() system call and looks up the names in two system databases. These are normally kept in the /etc/passwd and /etc/group files, although on some systems the information is stored somewhere on the network or in other, less standard, file locations. As a programmer, you do not have to worry about where the information is stored; generic functions are provided in the C library that read configuration files to determine where the information is stored, fetch the information, and return it to you transparently.

To demonstrate what ls gets from the kernel, run ls -ln:

drwxrwxr-x 5 500 500 1024 Aug 15 02:30 christid drwxr-xr-x 73 100 0 4096 Jan 18 12:48 johnsonm drwxr-xr-x 25 101 0 2048 Jan 12 21:13 kim drwxrwsr-x 2 1008 1008 1024 Jan 30 1996 tytso

The structure that represents entries in /etc/passwd (or equivalent databases for a system) is contained in <pwd.h>:

struct passwd { char *pw_name; /* Username */ char *pw_passwd; /* Password */ __uid_t pw_uid; /* User ID */ __gid_t pw_gid; /* Group ID */ char *pw_gecos; /* Real name */ char *pw_dir; /* Home directory */ char *pw_shell; /* Shell program */ };

  • pw_name is the unique username.

  • pw_passwd may be the encrypted password or something else related to authentication. This is system-dependent.

  • pw_uid is the (usually unique) number that the kernel uses to identify the user.

  • pw_gid is the primary group that the kernel associates with the user.

  • pw_gecos is a system-dependent member that stores information about the user. This generally includes the user's real name; on many systems, it is a comma-separated list of members that includes home and office phone numbers.

  • pw_dir is the home directory associated with the user. Normal login sessions start with this directory as the current directory.

  • pw_shell is the shell that is started on normal logins for the user. This is usually something like /bin/bash, /bin/tcsh, /bin/zsh, and so on. However, entries used for other purposes may have other shells. /bin/false is used for passwd entries that should not be used for logins. Specialized shells are often used for purposes beyond the scope of this book.

The structure that represents entries in /etc/group (again, or equivalent databases) is contained in <grp.h>:

struct group { char *gr_name; /* Group name */ char *gr_passwd; /* Password */ __gid_t gr_gid; /* Group ID */ char **gr_mem; /* Member list */ };

  • gr_name is the unique name of the group.

  • gr_passwd is the (usually unused) password. The same caveats apply to this member as to pw_passwd, only more so.

  • gr_gid is the (usually unique) number the kernel uses to identify the group.

  • gr_mem is a comma-separated list of group members. This is a list of usernames that are assigned to this group on a secondary basis (see Chapter 10).

There are two common reasons to access the system identification databases: if the kernel gives you a number and you want a name, or if someone or some program gives you a name and you need to give the kernel a number. There are two functions that look up numeric IDs, getpwuid() and getgrgid(), which take an integer ID and return a pointer to a structure containing information from the relevant system database. Similarly, there are two functions that look up names, getpwnam() and getgrnam(), and they return the same two structures. Here is a summary:

 

User Database

Group Database

Number

getpwuid()

getgrgid()

Name

getpwnam()

getgrnam()

All of these functions return pointers to structures. The structures are static structures that are overwritten the next time the function is called, so if you need to keep a structure around for any reason, you need to make a copy of it.

The four functions above are essentially shortcuts providing the most commonly needed functions for accessing the system databases. Lower-level functions called getpwent() and getgrent() iterate over the lines in the databases rather than search for some record in particular. Each time you call one of these functions, it reads another entry from the relevant system database and returns it. When you are finished reading the entries in, call endpwent() or endgrent() to close the relevant file.

As an example, here is getpwuid() written in terms of getpwent():

struct passwd *getpwuid(uid_t uid) { struct passwd *pw; while (pw = getpwent()) { if (!pw) /* error occurred; * fall through to error processing */ break; if (pw->pw_uid == uid) { endpwent(); return (pw); } } endpwent(); return NULL; }

28.1.1. Example: The id Command

The id command uses many of these functions and provides some excellent examples of how to work with them. It also uses some of the kernel features described in Chapter 10.

1: /* id.c */ 2: 3: #include <grp.h> 4: #include <pwd.h> 5: #include <sys/types.h> 6: #include <stdlib.h> 7: #include <stdio.h> 8: #include <string.h> 9: #include <unistd.h> 10: 11: void usage(int die, char *error) { 12: fprintf(stderr, "Usage: id [<username>]\n"); 13: if (error) fprintf(stderr, "%s\n", error); 14: if (die) exit(die); 15: } 16: 17: void die(char *error) { 18: if (error) fprintf(stderr, "%s\n", error); 19: exit(3); 20: } 21: 22: int main(int argc, const char *argv[]) { 23: struct passwd *pw; 24: struct group *gp; 25: int current_user = 0; 26: uid_t id; 27: int i; 28: 29: if (argc > 2) 30: usage(1, NULL); 31: 32: if (argc == 1) { 33: id = getuid(); 34: current_user = 1; 35: if (!(pw = getpwuid(id))) 36: usage(1, "Username does not exist"); 37: } else { 38: if (!(pw = getpwnam(argv[1]))) 39: usage(1, "Username does not exist"); 40: id = pw->pw_uid; 41: } 42: 43: printf("uid=%d(%s)", id, pw->pw_name); 44: if ((gp = getgrgid(pw->pw_gid))) 45: printf(" gid=%d(%s)", pw->pw_gid, gp->gr_name); 46: 47: if (current_user) { 48: gid_t *gid_list; 49: int gid_size; 50: 51: if (getuid() != geteuid()) { 52: id = geteuid(); 53: if (!(pw = getpwuid(id))) 54: usage(1, "Username does not exist"); 55: printf(" euid=%d(%s)", id, pw->pw_name); 56: } 57: 58: if (getgid() != getegid()) { 59: id = getegid(); 60: if (!(gp = getgrgid(id))) 61: usage(1, "Group does not exist"); 62: printf(" egid=%d(%s)", id, gp->gr_name); 63: } 64: 65: /* use getgroups interface to get current groups */ 66: gid_size = getgroups(0, NULL); 67: if (gid_size) { 68: gid_list = malloc(gid_size * sizeof(gid_t)); 69: getgroups(gid_size, gid_list); 70: 71: for (i = 0; i < gid_size; i++) { 72: if (!(gp = getgrgid(gid_list[i]))) 73: die("Group does not exist"); 74: printf("%s%d(%s)", (i == 0) ? " groups=": ",", 75: gp->gr_gid, gp->gr_name); 76: } 77: 78: free(gid_list); 79: } 80: } else { 81: /* get list of groups from group database */ 82: i = 0; 83: while ((gp = getgrent())) { 84: char *c = *(gp->gr_mem); 85: 86: while (c && *c) { 87: if (!strncmp(c, pw->pw_name, 16)) { 88: printf("%s%d(%s)", 89: (i++ == 0) ? " groups=": ",", 90: gp->gr_gid, gp->gr_name); 91: c = NULL; 92: } else { 93: c++; 94: } 95: } 96: } 97: endgrent(); 98: } 99: 100: printf("\n"); 101: exit(0); 102: }

The argument-handling code that starts on line 29 calls a few important functions. When called with no command-line arguments, id looks up information based on what user ran it and reports on that. getuid() is documented in Chapter 10; it returns the user ID of the process that calls it. getpwuid() then looks up the password file entry for that user ID. If id is given a username as a command-line argument, it instead looks up the entry based on the name that it is given, regardless of the ID of the user that runs it.

The id program first prints the user's numeric ID and name. The password file includes the name of the user's primary group. If that group exists in the group file, id prints its number and name.

Chapter 10 documents all the different forms of IDs that the kernel uses. id needs to use geteuid() and getegid() to check the effective uid and gid and print them if they differ from the real uid and gid. Again, we look up password and group structures by numeric ID.

Finally, id needs to print out all the supplementary groups. This is a little tricky because there are two ways to determine the list of supplementary groups. If a user is running id with no arguments, then id uses the getgroups() function to determine what groups the user is a member of. Otherwise, it gets a list of groups out of the group database.

Using getgroups() is preferable because it lists the groups that the current process belongs to rather than the groups that the user would belong to if he logged in at that moment. That is, if the user has already logged in and has been assigned a set of supplementary groups, and then the group database is changed, getgroups() gets the set of groups that apply to that login process; examining the group database gets the set of groups that will be applied to the user's next login session.

As documented in Chapter 10, the getgroups() function has an unusual (but convenient) usage: Call it once with 0 size and an ignored pointer (which can, as here, be NULL) and it returns the number of data items it wants to return. So then id allocates the exact-right-size list and calls getgroups() again, this time with the correct size and a list capable of holding the information it wants.

Next, id iterates over the list, getting the entries it wants from the group database. Note that this is different from using the group database to get the list of groups to which a user belongs. In this case, id is using the group database only to map group numbers to group names. A more efficient interface would use getgrent() to iterate over the group database and look up entries in the list, rather than the other way around; that is left as an easy exercise. Remember to use endgrent() when you are done. Not doing so would leave a file handle open, which may cause later code to fail if that code assumes (reasonably enough) that getgrent() starts with the first entry.

Note that there is no guarantee that the entries in the list returned by getgroups() is sorted in the same order that they appear in the group database, although it is often the case that they are.

If the user provided a username as a command-line argument, id needs to iterate over the group file, looking for groups in which the provided username is specified. Remember to call endgrent() to clean up after yourself!


       
     

    Категории