(7 ratings)   
By: astalaweb.net
One type of information that many programs require, is user information. This includes finding out the home directory of a given user, finding the list of groups a user belongs to, finding out who is logged on the system at a given time and so...
Added: 13 May 2008    Views: 31  
Keywords: computer   operating   system   linux   general   help   compile   unix   accessing   user   information   system  
Do you like this tutorial? Now you can support our team to add more :     
 
 
 

Accessing Info About User Accounts

The most basic info one would want to know about user accounts is - what user accounts exist on a system? or, is a given user name valid on this system? This information is traditionally kept in the file named "/etc/passwd", and on current Unix systems, in other files as well.


Structure Of The /etc/passwd File

The "/etc/passwd" file contains one line (or record) per user account, and may NOT contain any comments. Each line is made of a set of fields, separated by a colon (':') character. An example of such an line would be:

root:UQstWvHV002G1:0:0:root:/root:/bin/tcsh
This example shows a "root" (or superuser) account's entry. Most fields serve the same purpose on all Unix systems, and would be as follows:
User Name
The name used to identify this account in all human-readable printouts, as well as when the user tries to log on to the system.
Password
The encrypted password of the user (see the manual page of "crypt" for more information on password encryption). on many systems, this field may also serve as a marker that the password is kept elsewhere - see discussion of shadow passwords below.
User ID
The numeric ID of the given user on the system - this number is used internally by the system instead of the user name, mostly because numbers occupy a fixed size in binary format (usually 2 or 4 bytes), and are much faster to handle, to use as keys in search tables, etc.
User's Group ID
The numeric ID of a group this user belongs to. Every user belongs to at least one group on the system. Additional groups may be specified for a user using the /etc/group file.
GECOS field
This field is a general-purpose field in which a comment about this user may be kept. This will often contain the name of the account holder. Some systems use this to store the phone number, address and office room number of the account holder. The contents of this field are never crucial for the account's proper operation (on a non-modified system).
Home Directory
Full path to the home directory of this user. This will be used by the login program to place this user in this directory when logged on, for example.
Shell.
The default shell this user will use. This shell is launched for the user when logging on to the system.

The "/etc/passwd" file is readable for all users on the system, but writable only to its owner - root. This means that users cannot directly change the contents of this file - instead, there are specialized programs that allow a user to change her password, default shell and GECOS field. See the manual page for the "passwd" command for more info on these programs.


Getting A Given User's Info

The first API we will introduce is the function for fetching the "/etc/passwd" entry of a given user. There are two functions for doing that - getpwnam() and getpwuid().

getpwnam is used to fetch a "/etc/passwd" record of a user, given his user name. The function will return a pointer to a struct passwd, or NULL, if the user does not exist on the system. This structure is defined as follows:


struct passwd {
char *pw_name; /* user name */
char *pw_passwd; /* user 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 */
};
Here is an example of using this function:

#include <pwd.h> /* defines 'struct passwd', and getpwnam(). */
#include <sys/types.h> /* defines 'uid_t', etc. */

/* lets print some information about our root account. */
struct passwd* user_info = getpwnam("root");

/* make sure the root account exists. */
if (!user_info) {
printf("What?? no root account? this system is broken....\n");
}
else { /* information found - lets print it out. */
printf("Information for user 'root':\n");
printf(" User ID: %d\n", user_info->pw_uid);
printf(" Group ID: %d\n", user_info->pw_gid);
printf(" Home Directory: '%s'\n", user_info->pw_dir);
printf(" Default Shell: '%s'\n", user_info->pw_shell);
}

Just like we used the getpwnam() function, we may also use the getpwuid() function, supplying it the UID of the given user. For example, we know that the "root" account always has the UID of '0', so doing the above can be done by:



/* get the info of user with ID '0'. */
struct passwd* user_info = getpwuid(0);
/* continue like in the previous case.... */
if (!user_info) {
printf("What?? no root account? this system is broken....\n");
}
else {
.
.
}

One important note about these two functions: The structure they return is allocated in static memory. This means that the same structure will be re-filled with new information after each call to the same function, erasing any data it returned in a previous call. If you need to get info about two users and have it stored in memory, you should copy the structure returned from the first call, and store it in a struct passwd you allocated (as a local variable, or using malloc()). Here is an example:



/* this function copies one passwd struct into another. */
void
copy_passwd(struct passwd* from_pw, struct passwd* to_pw)
{
/* "string" fields need to be pre-allocated first. */
/* copy the user name field. */
to_pw->pw_user = (char*)malloc(strlen(from_pw->pw_user)+1);
if (!to_pw->pw_user) { /* out of memory?? */
fprintf(stderr, "copy_passwd: out of memory\n");
exit(1);
}
strcpy(to_pw->pw_user, from_pw->pw_user);
/* copy the passwd field. */
to_pw->pw_passwd = (char*)malloc(strlen(from_pw->pw_passwd)+1);
if (!to_pw->pw_passwd) { /* out of memory?? */
fprintf(stderr, "copy_passwd: out of memory\n");
exit(1);
}
strcpy(to_pw->pw_passwd, from_pw->pw_passwd);
to_pw->pw_uid = from_pw->pw_uid;
to_pw->pw_gid = from_pw->pw_gid;
/* copy the GECOS field. */
to_pw->pw_gecos = (char*)malloc(strlen(from_pw->pw_gecos)+1);
if (!to_pw->pw_gecos) { /* out of memory?? */
fprintf(stderr, "copy_passwd: out of memory\n");
exit(1);
}
strcpy(to_pw->pw_gecos, from_pw->pw_gecos);
/* copy the home directory field. */
to_pw->pw_dir = (char*)malloc(strlen(from_pw->pw_dir)+1);
if (!to_pw->pw_dir) { /* out of memory?? */
fprintf(stderr, "copy_passwd: out of memory\n");
exit(1);
}
strcpy(to_pw->pw_dir, from_pw->pw_dir);
/* copy the default shell field. */
to_pw->pw_shell = (char*)malloc(strlen(from_pw->pw_shell)+1);
if (!to_pw->pw_shell) { /* out of memory?? */
fprintf(stderr, "copy_passwd: out of memory\n");
exit(1);
}
strcpy(to_pw->pw_shell, from_pw->pw_shell);
}

/* here comes the main body of the code... */
/* allocating two local passwd structures. */
struct passwd user1_info;
struct passwd user2_info;

/* get info about account 'john'. */
struct passwd* user_info = getpwnam("john");
if (!user_info) {
printf("Account 'john' does not exist.n");
exit(1);
}
copy_passwd(user_info, &user1_info);
/* get info about account 'sarah'. */
user_info = getpwnam("sarah");
if (!user_info) {
printf("Account 'sarah' does not exist.n");
exit(1);
}
copy_passwd(user_info, &user2_info);

/* now do something with the information found... */

As you can see, the structure copying was done using a "deep copy" function - i.e. every field in the structure is copied in turn, including desired memory allocation. You ought to know that just copying the structure itself is not sufficient...
Note that in a real program, more testing should be made - what if one of the string fields has a NULL value?

Enumerating All User Accounts

The next API we will encounter is made of a set of functions that allow us to scan the full list of user accounts on our system. A set of functions are used here: getpwent(), setpwent() and endpwent().

The getpwent() function will fetch, on first invocation, the struct passwd info of the first user account on the system. On the next invocation it will fetch the info of the second user, and so on. After fetching the last user's info, if we call getpwent() again, it will return NULL. Here is a sample of printing out the list of user names on our system:



struct passwd* user_info;

printf("List of user names:\n");
for (user_info = getpwent(); user_info; user_info = getpwent() ) {
printf(" %s\n", user_info->pw_user);
}

As you can see, this is a rather simple piece of code.

The setpwent() function may be used to re-start a scan in the middle. After calling this function, the next invocation of the getpwent() will start scanning the user accounts list from the beginning.

The endpwent() function will terminate the current scan of the user accounts list. It is used simply to reduce resource usage. When we first called the getpwent() function, it actually opened the "/etc/passwd" file for reading, and kept the file open for the next reads. The endpwent() function simply closes this file, freeing any resources used to keep it open. thus, it is a good idea to call it when you are done handling the user's information.


Writing A User Authentication Function

Various types of programs need to authenticate users. This is often done by the user supplying a user name and a password, and the program verifying that it has this user name and password combination in its list of users.

The login program on Unix systems works in a similar fashion, thought a little more secure. When a user logges on, she is prompted for a user name and a password. The login program then uses this password as a key for encrypting some constant text, using the crypt() function. This produces a string that is then compared to a string stored in the password field of the user. If the strings match - the user is assumed to be authenticated, and is allowed to log in. The crypt() function performs an algorithm known as DES (Data Encryption Standard). One property of this algorithm is that there is no way to find the original password given the encrypted string that crypt() generates. This means that if a user got a copy of the "/etc/passwd" file, they won't be able to calculate the original passwords of users using a simple algorithm (this does not preclude using other methods to crack passwords...).

Lets see how one uses the crypt() function. It accepts two parameters. The first is the key (normally, the password to encrypt) and the second is called "salt". This is a two-character string that is used to further alter the results of crypt. One must make sure that the same salt is used when storing the password (actually the encrypted result of crypt()), and when authenticating the user. The login program often uses the first two characters of the user name as the "salt" string. crypt() returns an encrypted string when it returns, 13 characters long. Lets see a program that asks a user for their password, and compares it to the password, as taken from the "/etc/passwd" file.



#include <unistd.h> /* crypt(), etc. */
#include <pwd.h> /* getpass(), getpwnam(). */
#include <string.h> /* strcmp(), etc. */

/* buffers for reading in the user name and the password. */
char user[21];
char* password;
/* storing the encrypted password, and the salt. */
char* encrypted_password;
char salt[2];
/* user's "/etc/passwd" entry. */
struct passwd* user_info;

/* prompt the user for a user name. */
printf("User name: ");
fflush(stdout); {COMMENT_FONT}}/* flush the prompt to make sure the user sees it. */
fgets(user, 20, stdin);
/* fgets() stores also the new-line that the user typed in. so we */
/* need to locate the new-line character, and truncate it. */
if (strchr(user, '\n'))
(*(strchr(user, '\n'))) = '\0';

/* prompt the user for their password. the getpass() function */
/* prints the given prompt, turns off echo (so the password */
/* typed won't be seen on screen), and returns the string that */
/* the user types. */
password = getpass("Password: ");

/* find the user's encrypted password, as stored in "/etc/passwd". */
user_info = getpwnam(user);
if (!user_info) {
printf("login incorrect.\n");
exit(1);
}

/* take the salt as stored in the password field of the user. */
strncpy(salt, user_info->pw_passwd, 2);

/* encrypt the given password using the found "salt". */
encrypted_password = crypt(password, salt);

/* compare the results of crypt, with the user's stored password field. */
if (strcmp(user_info->pw_passwd, encrypted_password) != 0) {
printf("login incorrect.\n");
exit(1);
}

/* authentication succeeded... */
printf("login successful.\n");

The source code of this program is also available in the authenticate-user.c file. Note that on Linux systems based on the GNU C library version 2 (glibc2), there is a need to link this program with the crypt library, by adding the option "-lcrypt" to the compilation command.

Some notes about this code:
  • Better checking of the user's input should be performed (e.g. making sure the user actually typed something as a user name, making sure that the user name contains at least 2 characters (for the salt), etc.
  • The buffer returned by getpass() should be over-written immediately after use by the paranoid programmer, to avoid a possibility of someone somehow tracing it in memory while our process is still running.
  • Such an algorithm is useful only if it's done in a server, while the user uses some client program to connect to us. This way, we could terminate the connection in case of authorization failure. An interactive program that uses this code can be cracked easily (for example, the user uses a debugger or a disassembler program to skip the authentication function's execution altogether).
  • We always notify the user of the same error message, no matter what happened. For example, if we said "password incorrect" in the second message, the user would know that the user name by itself is valid - something they might have not known prior to the login attempt.

The (Non-Standard) Handling Of Shadow Passwords

Keeping the encrypted password of a user in the "/etc/passwd" file is considered insecure - even thought the encryption algorithm cannot be reversed (i.e. given the encrypted password, there is no algorithm to find out the original password), a user could copy out the "/etc/passwd" file of a system to their own system, and try running a password cracker that will in turn try to guess passwords for all accounts. On most systems, and with most users, this will allow them to guess passwords of a few accounts at least.

In order to reduce this risk, shadow passwords were invented. On a system that uses shadow passwords, the passwords of the users are kept in a different file, that is only readable to its owner - root. the password entry in the "/etc/passwd" file (for each user) will contain a special mark-up character, defining that the actual password is kept in the shadow file. This mark-up is done differently on different operating systems, and the shadow file's path is also different for different systems. Thus, although shadow passwords exist for at least a decade, there is no standard implementation for them.

When reading a user account's info (using getpwnam() for example), the password field in the returned structure will contain the place-holder string, rather than the encrypted password. This makes it impossible to write simple programs that authenticate users using their account's password. The solution to this problem is usually a specialized (non-standard) function supplied by the operating system, to perform such authentication. Such a password will normally accept a user name and an encrypted password, and will return '1' if this encrypted password matches that of the given user, or '0' if it does not. This still allows people to write password crackers, but then they must be ran on the same system whose accounts they are trying to break into, making it much easier to spot this activity and to block it (e.g. by disabling an account if too many authentication attempts are made against it, in a given period of time).


Association Of Extra Groups For Users

On a Unix system, it is possible to associate a user with more than one group. This is done using the "/etc/group" file, together with a set of APIs.


Structure Of The /etc/group File

The "/etc/group" file is a text file that contains a set of lines, each defining membership of a list of users in a given group. Each group may be defined by one or more lines, and the users of this group include all the users defined in all these lines, as well as the users having this group's GID (Group ID) in their "/etc/passwd" entries. Here is an example of a valid "/etc/group" line:

bin::1:root,bin,daemon
This example shows some information about the "bin" group. This line is composed of a set of fields, separated by a colon (':') character (like in the "/etc/passwd" file). The fields serve the following purposes:
Group Name
The name of the group this line defines.
Password
The password for this group. Normally empty, and implying that no password is required for the group.
Group ID.
The numeric ID of the given group.
Users List
A list of user names that belong to this group. The names are separating using comma (',') characters.
Thus, the above line states that the "bin" group has a GID of '1', and has users "root", "bin" and "daemon" as members. Again, remember that there may be several lines for the group "bin" in this file, and they should all have the same GID. The order of such lines is not important.

Finally, note that much like the "/etc/passwd" file, the "/etc/group" file is readable by anyone, and writable only by its owner - root. However, unlike for the "/etc/passwd" file, there are no standard programs that allow changing the contents of the "/etc/group" file.


Listing The Users In A Given Group

The first API we will deal with is used to get the list of users belonging to a given group. This is done using the getgrnam() or the getgrgid() functions.

The getgrnam() function gets a group name as its parameter, and returns a pointer to a struct group containing information about the given group. The function will return NULL if the group does not exist on the system. The group structure that is returned is defined as follows:


struct group {
char *gr_name; /* group name */
char *gr_passwd; /* group password */
gid_t gr_gid; /* group id */
char **gr_mem; /* group members */
};
Note that the gr_mem field is made of a list of user names, terminated by a NULL pointer. This group will contain all members of the group - no matter if they are defined on several separate lines. However, users associated with the group only via the GID field in their "/etc/passwd" entries (and not via proper lines in the "/etc/group" file) would NOT be included in this list. This makes this function somewhat broken.
Here is an example of a code that prints out the list of members in the "strange" group:


#include <grp.h> /* defines 'struct group', and getgrnam(). */
#include <sys/types.h> /* defines 'gid_t', etc. */

/* get the information about the "strange" group. */
struct group* group_info = getgrnam("strange");
/* make sure this group actually exists. */
if (!group_info) {
printf("group 'strange' does not exist.\n");
}
else {
char** p_member;

printf("Here are the members of group 'strange':\n");
for (p_member = group_info->gr_mem; *p_member; p_member++)
printf(" %s\n", *p_member);
}

The getgrgid() function works the same as getgrnam(), except that its parameter is the numeric GID of the desired group. hopefully you would be able to write your own example of using this function.

One last note about the getgrnam() and getgrgid() functions is that just like the getpwnam() function, they return pointers to statically allocated structures, and thus erase the results of the previous call on each consecutive call.


Enumerating (almost) All Groups

Similarly to the API used to enumerate user accounts, there are APIs used to enumerate "/etc/group" file entries. There are getgrent(), setgrent() and endgrent(). they work in exactly the same way as their counter APIs for the "/etc/group" file. Here is a quick example:



/* start the scan of the "/etc/group" file. */
struct group* group_info = getgrent();

/* loop over all group entries, until a NULL pointer is returned. */
printf("Here is a list of all groups found in '/etc/group':\n");
for ( ; group_info; group_info = getgrent() ) {
printf(" %s\n", group_info->gr_name);
}

We won't demonstrate the usage of setgrent() and endgrent() - that should be obvious by now. If it is not, re-read the user accounts enumeration section.

Groups In /etc/group Vs. Groups In /etc/passwd

As it was mentioned before, groups may be defined in two places: by using the GID field of a user's entry in the "/etc/passwd" file, and by an entry in the "/etc/group" file. A group specified in the "/etc/group" file always has a group name, while a group specified in the "/etc/passwd" file does not have a name (unless there is a group with the same GID defined in the "/etc/group" file).

Another difference (already mentioned before) is that the getgr*() functions will show members specified in the "/etc/group" file, but will not show members specified via the "/etc/group" file.

Finally, we will refer the user to the groups command. This command will print the names of all groups the currently logged on user belongs to, or all groups a given user belongs to. This list of group will contain all relevant entries, whether found in the "/etc/group" file, or in the "/etc/passwd" file. Read the manual page for the "groups" command for more information.


Determining User Info At Run-Time

One of the things programs often need to know is which user they perform as, what groups they belong to, etc. For this, we should note that each process running on a Unix system, has a specific UID and a specific set of GIDs. These are usually the UID and GIDs of the user running the process, but there are exceptions.


Which User Is My Process Running As?

Determining the UID of a process is done using the getuid() system call, and a set of its cousins that will be discussed when we explain about Set UID programs below. This system call gets no parameters, and returns the effective UID of the process. Here is an example that prints out the name of the user executing this process:



/* Find the "/etc/passwd" entry of the UID of our process. */
struct passwd* user_info = getpwuid(getuid());

/* Print out the results. */
if (!user_info)
printf("Error, cannot determine who i am.\n");
else
printf("A-ha! I am %s\n", user_info->pw_name);

As the getuid() system call cannot fail, there is no need (and no way) to check its success.

Which Groups Is My Process Associated With?

Sometimes our program might want to know which groups its process (or rather, it's process's user) is part of. For example, consider a file manager that allows us to change the group owning a file. It will present us with a menu containing all groups which we belong to. For this, the getgroups() system call may be used. This system call accepts two parameters: the maximal number of groups IDs we want to receive, and an array into which the GIDs will be stored by the system call. getgroups() returns the number of GIDs actually stored in the array. A special case is if we supply '0' in the first parameter. In this case, the array is unmodified, and the return value is the total number of groups associated with the process. Here is how we can receive all the groups in a single call:



#include <unistd.h> /* getgroups(), etc. */

/* this array will be used to store the GIDs. */
gid_t** group_ids;
int num_groups;

/* first, check how many groups our process actually has. */
num_groups = getgroups(0, group_ids);
if (num_groups < 0) {
perror("getgroups");
exit(1);
}
if (num_groups > 0) {
int i;

group_ids = (gid_t**)malloc(num_groups * sizeof(gid_t));
getgroups(num_groups, group_ids);
printf("GIDs of our process:\n");
for (i=0; i<num_groups; i++)
printf("%d,", group_ids[i]);
printf("\n");
}

Note that we called getgroups() twice - once to find the number of groups (so we could allocate a large enough array), and once to actually get the GIDs. We could then translate the GIDs into group names (using the getgrgid() function we saw earlier), and present that to the user (in our file manager menu's example).

The Sick Case Of Set-User-ID (suid) Programs

If you remember, we mentioned earlier that the "/etc/passwd" file is only writable for "root". Yet, there is a program named "passwd" that allows other users to change the contents of certain fields in this file. You might wonder how this is done. The idea is rather simple. If you check out the permissions of the "passwd" program, you will see something like this (using 'ls -l /usr/bin/passwd'):


-r-sr-xr-x 1 root bin 15796 Apr 23 1997 /usr/bin/passwd
As you can see, instead of an 'x' for the owner, there is a lower-case 's'. This indicates that this program has the "set user-ID" (or SUID for short) bit - set. When the operating system launches a program that has the SUID bit set, it will give the process running it the identity of the user that owns the file - in this case, "root". This means that this process has the same access permissions as "root" has, rather than as the user that started this process. This way, when user "john" runs the "passwd" program, he may alter the "/etc/passwd" file, that he could not alter directly. It is up to the program to make sure that he only may change fields of his own account, and indeed, many security holes on Unix systems are due to bugs in SUID programs, that allow users that run them to perform operations they shouldn't be able to.

The method by which the operating system handles SUID programs, is by giving each process two identities: the real UID, and the effective UID. The real UID is the UID associated with the process that launched our process. The effective UID is the same as the real UID for normal programs, or it is the owner of the executable file, for SUID programs. These changes may also be done using the setuid() and seteuid() functions, under various restrictions. These system calls are used (for example) by the "login" program (that runs as user "root" initially), to spawn a shell whose real and effective UID are those of the user that logs in.

To find the real UID of a running process, we may use the getuid() system call. To find the effective UID - we use the geteuid() system call. If you remember, when we demonstrated finding the user name of our process earlier, we used the getuid() system call. However, if we wanted to find what actions the process will be actually able to do, we should have checked the effective UID of the process - this is because we need to handle the case of SUID programs - where the effective UID and the real UID are different.

Note: on Unices derived from the BSD system, there is a third type of UID, called the stored UID, that further complicates the rules of setting a process's real and effective UID.


Who Is Currently Logged On?

One thing that might interest our program is - who is currently logged on to our system? For example, the finger program prints that information our to users. Messaging programs (write, talk) use this information to find if the person we try to send a message to is logged on the system or not.

For this purpose, the "login" program writes down information in a file named "utmp" (often residing in the "/var/log", "/var/adm" or "/var/run" directory). This file is a binary file, and each entry is a record of a fixed size. There is no standard API used to read the contents of this file, but there is a data structure, defined in the "utmp.h" include file, that gives us the exact format of a record. Here is how it is defined on a Linux system:


struct utmp {
short ut_type; /* type of login */
pid_t ut_pid; /* pid of process */
char ut_line[UT_LINESIZE]; /* device name of tty - "/dev/" */
char ut_id[2]; /* init id or abbrev. ttyname */
time_t ut_time; /* login time */
char ut_user[UT_NAMESIZE]; /* user name */
char ut_host[UT_HOSTSIZE]; /* host name for remote login */
long ut_addr; /* IP addr of remote host */
};
This structure may be defined differently on different operating systems, but it will usually contain at least the user name and the time of the login. For our purpose, the login name is all that matters. Here is a sample program that lists the names of the currently logged in users:

#include <stdio.h> /* printf(), etc. */
#include <utmp.h> /* struct utmp, etc. */
#include <string.h> /* strncpy(), etc. */
#include <unistd.h> /* open(), etc. */
#include <fcntl.h> /* O_RDONLY, etc. */

/* full path for the UTMP file on our sample system. */
#define UTMP_PATH "/var/run/utmp"

/* buffer to read one utmp record. */
struct utmp utmp_entry;
/* buffer to store a user name. */
char user_name[UT_NAMESIZE+1];
/* file descriptor for the "utmp" file. */
int fd;

/* open the utmp file for reading. */
fd = open(UTMP_PATH, O_RDONLY);
if (fd < 0) {
perror("open");
exit(1);
}

printf("Currently logged-in users:\n");
/* start scanning the file, entry by entry. */
while (1) {
int rc = read(fd, &utmp_entry, sizeof(utmp_entry));
if (rc < 0) {
perror("read");
exit(1);
}
if (rc == 0) /* end of file - exit the reading loop. */
break;

/* This is Linux specific - only records whose ut_type field is */
/* USER_PROCESS, represent logged in users. the rest are temporary */
/* records of various types. */
if (utmp_entry.ut_type != USER_PROCESS)
continue;

/* copy the user name field to our user name variable. */
strncpy(user_name, utmp_entry.ut_name, UT_NAMESIZE);
/* make sure this string is terminated with a null character. */
user_name[UT_NAMESIZE] = '\0';

printf("'%s',", user_name);
}

printf("\n");
close(fd);

The source code of this program is also available in the show-logged-users.c file.

A few notes are in place:
  • The code above is not portable, as not all systems have the ut_type field in a utmp record.
  • String fields (user name, host name) are terminated by a null character ('\0'), unless their contents are as large as the field's size. In such a case, we must add the missing null character before treating the value as a string.
  • The code showed here is buggy. just think what happens if in the middle of scanning the file a new user is logged in. To avoid this, some file locking is necessary (e.g. using the fcntl). However, this should not be done for a long period of time, or else no user will be able to log in (the login program will get blocked when it tries to update the utmp file) - so try this only at home - not on a production system.
  • The same user may have several records in the utmp file - one for each session the user currently has. If we wanted to only show the user names, we should remove these duplicates.
  • On some systems, there might be a non-standard API for reading from (and possibly writing to) the the utmp file. It is better to use that API than to use this code, thought it will be even less portable.

Who Has Been Using My System Lately?

Much like the utmp file stores records of currently logged in users, the wtmp file stores historic records of login and logout operations. This can be used to find out when a user was last logged in, or how many hours they have spent on the system in the past week. The wtmp file contains records that are appended by the various login programs. Their format is exactly like the format of utmp record, with two exceptions:

  • A null user name in a record indicates a logout record. To find the matching login record, scan the wtmp file backwards, until the first record with the same device name field (ut_line);
  • A record with the value '~' in its terminal name, and a value of "shutdown" or "reboot" in its user name field, indicates a shutdown operation or a boot operation, respectively. In such a case, there might be missing logout entries for users that were logged on during the shutdown, and programs that process the wtmp file should take this into account.
  • Some other record types might exist - read the manual page about wtmp for info about how your system handles this file.
We will leave it up to you to write a program that parses the wtmp file. Note that this file might grow to a very large size on busy systems, so such a program should be very efficient. An example of such a program is the "last" program, that shows you the list of logins to the system from the most recent - backwards.

References To Other System Databases

There are many other files on Unix systems that serve as databases. For example, the "/etc/shells" file defines the list of valid user shells, and is consulted by the "chsh" (change shell) program that allows a user to set their login shell. There are databases to hold mapping of host names into IP addresses ("/etc/hosts"), and so on. Some of these database files are standard on most Unix systems. Some are specific to classes of Unix systems. Often, a specific section of the on-line manual (section 4 or section 5, depending on the system type) documents the structure of these files. You might want to take a look at the files found in directory "/usr/man/man5" or "/usr/man/cat5" to get a clue of what database files exist on your system.

About the Author :
 Rate this tutorial : Rate 1Rate 2Rate 3Rate 4Rate 5
  |    Add to Favorites
  |    Send to Friend
  |    Print
Comments