Topic : Accessing User Information
Author : LUPG
Page : << Previous 2  Next >>
Go to page :


   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

Page : << Previous 2  Next >>