Originally Published: Monday, 16 October 2000 Author: Elmo Recio
Published to: enhance_articles_sysadmin/Sysadmin Page: 1/1 - [Printable]

Unix Password Management

Part of the neatness of UNIX is that you can just about do everything programmatically in the same way that you do it by hand. For example, you ever wonder why chmod() is called such? Well, in writing my admintool program, I found out that if I wanted to change the mode of a directory or ownership programmatically I would just call a function named chmod(). So I started to explore other aspects of the system calls and came across some really useful ones.

   Page 1 of 1  

Part of the neatness of UNIX is that you can just about do everything programmatically in the same way that you do it by hand. For example, you ever wonder why chmod() is called such? Well, in writing my admintool program, I found out that if I wanted to change the mode of a directory or ownership programmatically I would just call a function named chmod(). So I started to explore other aspects of the system calls and came across some really useful ones.

User ID

Well, the first problem I ran into when programming with these UNIX system calls, was that after I started using the shadow password database, it would coredump. I figured out that it was because I was running the program as a normal user. I had to figure out some way of identifying the current user running the program. Here is where getuid() and geteuid() came in handy.

#include <unistd.h>
uid_t getuid(void);
uid_t geteuid(void);

getuid returns the real user ID of the current process.

geteuid returns the effective user ID of the current process.

The real ID corresponds to the ID of the calling process. The effective ID corresponds to the set ID bit on the file being executed.

OK, first off, when you are a program, you can assume other user id's temporarily. I'll show you how to do that later. In cases when programs temporarily change their user id's and become someone else, issuing the command geteuid will return the user id of the program at the time the system call was issued. Issuing the command getuid will always return the overall user id of the program. In other words, it will return the user id of the person running the program, even if it temporarily changed its id.

For example, let's pretend that you are user id #2, and a program that you ran was written to change to root (user id #0) when it gets to a certain point (let's say half way through.) When it switches to user id 0, and you issue both system calls, getuid will return 2, but geteuid will return 0.

Group ID

In addition to being able to identify your user id and effective user id, you can identify your group id, and your effective group id. From the manual pages:

#include <unistd.h>
gid_t getgid(void);
gid_t getegid(void);

getgid returns the real group ID of the current process.

getegid returns the effective group ID of the current process.

The real ID corresponds to the ID of the calling process. The effective ID corresponds to the set ID bit on the file being executed.

Just as above with the user id's, these two functions return the group id of the person running the program. getgid returns the group id of the person who ran the program. Whereas, getegid returns the group id of the program at the time the system call was issued.

Sample Program

The following is a sample program to demonstrate the above functions:

#include <stdio.h>
#include <unistd.h>
int main()
{

uid_t user_id;
uid_t eff_user_id;
gid_t group_id;
gid_t eff_group_id;

user_id = getuid();
eff_user_id = geteuid();
group_id = getgid();
eff_group_id = getegid();

printf("User Id: %ld, Group Id: %ld user_id, group_id);
printf("Eff User Id: %ld, Eff Group Id: %ld eff_user_id, eff_group_id);

return 0;
}

The sample program when run should produce similar results when run (note that I am on a large system, hence the high UID number, your number might be slightly lower.)

dunx1:~$ ./ex2-1
User Id: 11567, Group Id: 35

Eff User Id: 11567, Eff Group Id: 35

/etc/passwd

OK, so you are probably thinking that this is some special hacking (read: cracker) section. Well, it's not. In fact, I'll assume you are already familiar with the layout of the /etc/passwd file. Additionally, if you have shadow passwords enabled, then you understand the existence of /etc/shadow. The point of this section is to get you efficiently use the password database routines.

Passwd struct

And that's what the passwd struct is for; the passwd structure is defined in <pwd.h> 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 */ }

As you can see there is a member for the name, password, user id, group id, gecos field, the user's home directory and their shell. The password database routines for manipulating this struct is as follows:

#include <pwd.h>
#include <sys/types.h>

struct passwd *getpwent(void);
void setpwent(void);
void endpwent(void);

Getting a password entry

getpwent returns a pointer to a static passwd struct. It does all the work of opening /etc/passwd and fetching a line for you. You don't have to fiddle with string manipulation functions or anything like that (IE: strdup, strchr &c.) Think of it as instant data... just add water. Anyway, the point: when the function returns, it'll give you a pointer to a password file entry. Then you can start grabbing data from it and doing what you will with the data.

Usually, though the first entry is not the one you want. Remember that since the getpwent starts at the beginning of the password database, it's usually going to return root, bin, mail, all of those rather uninteresting users. Let's say you want to get user jshmoe's data. The main problem is that the data you want, most times is at the end of the password database file. So what you can do (at this point) is loop through calling getpwent testing for a particular name and if that name matches, then you're all set. Otherwise if the pointer returned from getpwent is null, you have reached the end of the password database file, and it's time to give up.

Checking for errors

This is actually quite simple to check for errors. getpwent returns a null if it encounters an error. The way to determine if it was an error, or if it was just normal termination of the password database, is by using a nice little function called perror(), errno. This function prints out whatever errors may have been set by the calling function. Here's the catch: you have to see what errno is set to, because you don't know beforehand what kind of error it is. The following code snippet might do it:

struct passwd *p_entry;
int errno=0;

p_entry = getpwent(); /* get password entry */

if ( p_entry == NULL ) {
if (errno!=0) {
perror("getpwent");
exit(-1);
}
}

Line 66 initialises errno to zero so that we can check later if that variable has been touched by getpwent. If it has, then we print out an error, otherwise we handle the null as if the end of the password file has been reached.

Starting over

setpwent rewinds the cursor and puts it at the beginning of the file. For example, if you are at line five of the password database file (in your loops using getpwent) then it will put you back to the beginning of the file, or at line one.

Cleaning up after yourself

endpwent function closes the database file. This is somewhat important incase there are other users attempting to update, or read from the password file. (i.e.: someone's changing their passwords.) It's simple to use and there aren't any errors associated with it. Although, you should not call this function if you want to rewind the cursor back to the beginning. Yeah, it will work, but by doing this you use up file descriptors, which isn't good coding style.

Does it have to be the password file?

OK, so you are thinking that these functions are so cool that you'd like to use it for your own program's user database. Well that's where fgetpwent comes in handy.

#include <pwd.h>
#include <stdio.h>
#include <sys/types.h>

struct passwd *fgetpwent(FILE *stream);

This means that you can use the password file function fgetpwent just like you would do it against the system password database file with getpwent. Here the only difference is that you send it a pointer to a previously declared FILE stream. Everything that we said about the getpwent function applies to this function. The only difference is that it's doing it against a file stream that you give it.

No looping required

'So looping sucks,' you say! No problem, you can avoid that looping mess by getting the entry from the password database either by user name, or user id. Consider the following two functions:

#include <pwd.h>
#include <sys/types.h>

struct passwd *getpwnam(const char * name);
struct passwd *getpwuid(uid_t uid);

Get it by user name

So, you have the user's name, and you don't want to loop. No problem! You can get a password record from the system by user name. Using getpwnam returns a static pointer to password struct just like getpwent. Except, you feed it a user name (a null terminated character string) that you want to retrieve.

Get it by user id

Alternatively, if you want to get a password database record by user id, the number assigned to a user, as we talked about above, you use the getpwuid function. It'll return a password struct with the appropriate fields filled out from the system's password database file corresponding to the user id requested.

Adding an entry

And the part we've all been waiting for! Yes folks, writing to the sacred file! For this we use the putpwent function. Consider the following:

#include <pwd.h>
#include <stdio.h>
#include <sys/types.h>

int putpwent(const struct passwd *p, FILE *stream);

The function putpwent allows you to write to a password database of your very own; even the /etc/passwd file! Unlike the function getpwent, where it automagically assumed that you are talking about /etc/passwd, putpwent (for safety reasons) must explicitly be told which file stream it's writing to.
The error codes returned by putpwent are ENOSPC, EIO, or EPIPE. ENOSPC means the disk that function was writing to is full; it's out of space. EIO means that there was an I/O error and it cannot write to the disk. This is usually the case with NFS when the network's gone down; or when the piece of tape holding the ribbon cable to my hard disk has come off. EPIPE means that it's trying to write to a stream (which is a pipe) and the destination has closed it's end of the pipe.

Sample Programs

The following programs demonstrate the functions we have talked about in this section.

Scanning for passwords and error handling

Name: ex2-2.c Covers: getpwent, and errno

#include <stdio.h>
#include <pwd.h>
#include <sys/types.h>

#include <errno.h> /* for perror() */

int main ()
{
struct passwd *p_entry;
int errno=0;

p_entry = getpwent(); /* get a password entry */

if ( p_entry == NULL ) {/* have we reached the the end? */

if (errno!=0) { /* is it an error? */
perror("getpwent");
exit(-1);
}
printf("Got to the end of the file!
exit(0);
}

printf("Username is %s, home directory is %s
p_entry->pw_name,
p_entry->pw_dir);

}

Putting and Getting a record from a custom file

Name: ex2-3.c Covers: fgetpwent, putpwent

#include <stdio.h>
#include <pwd.h>
#include <sys/types.h>

#include <errno.h> /* for perror() */

int main ()
{
FILE *mypasswd;

/* File stream pointer */
char username[]="jshmoe";
char homedir[]="/tmp/jshmoe";
struct passwd *p_entry;
/* pointer to hold the
password info */
int errno=0;

p_entry = getpwent()
/* get password entry */
if ( p_entry == NULL ) {
if (errno!=0) {
perror("getpwent");
exit(-1);
}
printf("Got to the end of the file!
exit(0);
}
printf("System Username is %s, home directory is %s
p_entry->pw_name,
p_entry->pw_dir);

/* Now let's write it to our home grown database file
using putpwent().
Let's change the system's username to something
different like, jshmoe, and the user's home directory
to '/tmp/jshmoe' */

p_entry->pw_name = username;
p_entry->pw_dir = homedir;

mypasswd = fopen("mypasswd.db","w");
if ( mypasswd == NULL ) {
/* if we cant get a file stream pointer, that means
that an error has occured. Print out the error */
perror("fopen");
exit(-1);
}

if ( putpwent(p_entry, mypasswd) < 0 ) {
/* If there's an error the return value will be < 0 */
perror("putpwent");
exit(-1);
}
fclose(mypasswd);


/* Now let's read from our home grown database file by
using fgetpwent() */

mypasswd = fopen("mypasswd.db","r");
if ( mypasswd == NULL ) {
/* if we cant get a file stream pointer, that means
that an error has occured. Print out the error */
perror("fopen");
exit(-1);
}
p_entry = fgetpwent(mypasswd);

printf("mypasswd.db username is %s,
home directory is %s
p_entry->pw_name,
p_entry->pw_dir);
}

Getting a record by name, then by user id.

Name: ex2-4.c Covers: getpwnam, getpwuid

#include <stdio.h>
#include <pwd.h>
#include <sys/types.h>
#include <errno.h>

int main()
{
struct passwd *p_entry1, *p_entry2;

/* We are getting the password entry for OUR own user id,

we get our own user id by *remember* getuid()!! =) */
p_entry1 = getpwuid(getuid());
printf("username = %s = %s
"userid = %ld group id = %ld
"real name = %s directory = %s
"primary shell = %s
p_entry1->pw_name, p_entry1->pw_passwd,
p_entry1->pw_uid, p_entry1->pw_gid,
p_entry1->pw_gecos, p_entry1->pw_dir,
p_entry1->pw_shell);

/* Now, let's get the same data, but based off of the
user name that we just got (pw_name) through
the function getpwnam() */

p_entry2 = getpwnam(p_entry1->pw_name);
printf("= %s = %s
"userid = %ld group id = %ld
"real name = %s directory = %s
"primary shell = %s
p_entry2->pw_name, p_entry2->pw_passwd,
p_entry2->pw_uid, p_entry2->pw_gid,
p_entry2->pw_gecos, p_entry2->pw_dir,
p_entry2->pw_shell);

return 0;
}

/etc/group

In addition to functions for manipulation of the password database, there are also a set of functions to manipulate the group database's group and group membership. Again, I won't go over the details of the /etc/group file, but suffice it to say that it's similar to the /etc/passwd file. It has four fields: group name, group password, group id number, and a list of members of the group.

The group struct

The group struct is defined in <grp.h> as follows:
char *gr_name; /* group name */
char *gr_passwd; /* group password */
gid_t gr_gid; /* group id */
char **gr_mem; /* group members */
}

Big surprise, just as in the file, the struct has a member for group name, group password, group id (which is of type gid_t - remember that?) and a two-dimensional array of strings (user names of group members.)
What's interesting about the group structure, is its last member. As I pointed out a second ago, it's a two-dimensional array. This makes scanning through the member list a bit tricky (not difficult, but interesting.) The key to know is that the last pointer at the end of the list of strings, is a null pointer. You want to check for this when cycling through the member list. The following is a snippet of code that does just that:

231struct group *g_entry;
g_entry = getgrent();

for (i=0;g_entry->gr_mem[i]!=0;++i) {
printf("%s ",g_entry->gr_mem[i]);
}

Getting Groups

The group system calls for manipulating the group database are as follows:

#include <grp.h>
#include <sys/types.h>

struct group *getgrent(void);
void setgrent(void);
void endgrent(void);

Getting group records from the group database is very similar to getting user names from the password database. The function getgrent automagically opens the group database and retrieves the first group. Each time getgrent is called it will retrieve the next record in the group database. Hence, if you want to scan the group database, you can just call getgrent until a null pointer is fetched. This null pointer tells you that you have reached the end of the file.

Starting Over

You can rewind the database and start over (from the first record) by calling setgrent. This function makes the internal cursor go to the beginning of the group database file and start with the first one without closing the file.

Cleaning up after yourself

If you are done scanning the group database file, then you should close the file. Call endgrent. This is good practice as it frees up the file from possible corruption through other processes attempts to access the file at the same time. However, it's not good programming practice to call endgrent, if you want to start searching the group database from the beginning. Yeah, it'll work, but it needlessly uses up file descriptors.

Does it have to be the group database?

No! In fact, there's a function similar to the fgetpwent, it's called the fgetgrent ... as if you couldn't already guess! The function is defined as follows:

#include <grp.h>
#include <stdio.h>
#include <sys/types.h>

struct group *fgetgrent(FILE *stream);

The function fgetgrent works exactly the same as fgetpwent, in that it takes an open and valid pointer to the file stream as an argument. Then, like getgrent, it cycles through the custom group database file returning a pointer to a group struct.

No looping required!

You can also use two functions to get the group data directly, either by name or by group id. These functions are provided to make all of our lives much easier! The following are the functions defined:

#include <grp.h>
#include <sys/types.h>

struct group *getgrnam(const char *name);
struct group *getgrgid(gid_t gid);

Get it by group name

Locating the group record by name is a cinch when you use getgrnam and send it, as an argument, the C string consisting of the group's name. It returns a pointer to the group structure filled with group data if it can find the group you asked it to look for. Otherwise, the function returns a null pointer, which basically means that you're out of luck.

Get it by group id

Additionally, locating the group by id is just as easy when you use getgrid. This function takes as one argument the group id number and based on that will return you the record with that group id number associated with it. If it cannot find that group then it returns a null pointer. Pretty simple huh?

Adding an entry

Unfortunately, they don't have any convenient functions to put entries into the group database. However, you can easily use sprintf to format your group struct data and write the string to the file. You can even make your own putgrent function! We have supplied such a function (identical to the putpwent function) for you! Consider the following code:

#include <stdio.h>
#include <grp.h>
#include <sys/types.h>
#include <errno.h>

int putgrent(const struct group *g, FILE *stream)
/* This function mimics the behaviour demonstrated by
putpwent() except that it operates on groups instead */
{
int temp_length=0; /* length of string to calloc() */
int i=0; /* our member iterator */
char temp_double[27]; /* holds the string of gid */
char *temp_holder; /* create a temporary holder
for our output data */

/* This should never be the case! */
if (g==NULL || stream==NULL) return -1;

/* first thing we want to do is calculate
how long our line's going to be. We do
this by getting the strlen() of each of
the group struct's members */
temp_length=strlen(g->gr_name);
temp_length+=strlen(g->gr_passwd);

/* now in order to get the length of the string
version of the group ID, we have to convert it
then count the characters */
sprintf(temp_double, "%ld", g->gr_gid);
temp_length+=strlen(temp_double);

/* now we have to iterate through the loop to
get our group members' lengths. */
for(i=0;g->gr_mem[i]!=0;++i)
temp_length+=strlen(g->gr_mem[i]);

/* Cool! We got the length of the line, let's
add `i' to it because we are going to need
to put coma's in there between the names,
and add 10 for extra colons, and padding! */
temp_length+=(10+i);

/* allocate the number of chars necessary for line */
temp_holder = (char *)calloc(temp_length,sizeof(char));
if ( temp_holder == NULL ) return -1;

/* Format the name and password and group id's */
sprintf(temp_holder,"%s:%s:%ld:",
g->gr_name, g->gr_passwd, g->gr_gid);

for(i=0;g->gr_mem[i]!=0;++i) {
strcat(temp_holder,g->gr_mem[i]);
strcat(temp_holder,",");
}

/* And finally replace extra coma with a newline! */
temp_holder[strlen(temp_holder)-1] = '
/* write the line to a file stream given to you */
if ( fputs(temp_holder, stream) < 0 )

return -1;
return 0;
}


Shadow Passwords

If you are using shadowed passwords, most modern UNIX systems are now doing so, then you will have to concurrently use various other functions when accessing the password database. Using the getpwent function doesn't search the /etc/shadow file for the person's password. For manipulating shadowed passwords you will have to use getspent family of functions instead.
From the manual pages:

#include <shadow.h>

struct spwd *getspent();
struct spwd *getspnam(char *name);

void setspent();
void endspent();

struct spwd *fgetspent(FILE *fp);
struct spwd *sgetspent(char *cp);

int putspent(struct spwd *p, FILE *fp);

As you have probably guessed, the functions above operate on the shadowed password database file almost identically as they do on the regular password database file. Note the similarity in the spelling to the normal functions (easy enough to remember which one does what since s stands for shadowed.) The one difference you have noticed however, is that these functions return a struct of type spwd.
The spwd struct differs from the standard password database file in the following ways:

#include <shadow.h>

struct spwd {
char sp_namp; /* user login name */
char sp_pwdp; /* encrypted password */
long sp_lstchg; /* last password change */
int sp_min; /* days until change allowed */
int sp_max; /* days before change required */
int sp_warn; /* days warning before expiration */
int sp_inact; /* days before account becomes inactive*/
int sp_expire; /* _date_ when account expires */
int sp_flag; /* reserved for future use */
}

These fields, like the password database file are delimited by colons in the /etc/shadow file. A sample entry in the /etc/shadow file would look something like this:

barry:$1$0ZWTtM5U$yN5VxD9$j1ndsyQ:11112:0:99999:7::11322:

The first field is, of course, the user's login name, a null terminated character string. The second field is a standard crypt'd password, a null terminated character string. The third field, sp_lastchg, is the number of days since January 1, 1970 that the password has changed.

The sp_lastchg is a long integer and is a bit tricky in that to get the actual date that the person changed their password you would have to pass it through the C-time and date functions. Otherwise you can calculate it yourself (not recommended.) In the above example, the user changed his password 11112 days after January 1, 1970.

A practically useless field, sp_min, holds the number of days before which a password may not be changed. I say practically, because the user ought to be encouraged to change their password as often as is reasonably possible (for security reasons.) To help you to encourage users to change their password there's the sp_max field. This field sets the number of days before users must change their password. Some users, however, may not heed your encouragements, or may forget that their password is to expire. To help remind users, we have the sp_warn field; it contains the number of days before the password expires that the users are warned of this.

On large UNIX systems, universities for example, it may be difficult to track which users leave permanently from the organisation. For automatic account disabling, we have the sp_inact field. This field contains the number of days after the password expires that the account is considered expired and is diabled. However, if the administrator knows beforehand that users are to have an account for a particular period of time, they can set sp_expire. This field contains the number of days before an account is disabled.

As you can see, this shadowed password struct allows for a more comprehensive and complex control over the the user's account versus the older password database file. It increases security and makes the system administrator's life a lot easier. And now that you have been introduced to the shadowed password struct, you can write small custom programs to manipulate the shadowed password database, augmenting your homebrew administration toolkit.

Locking and Unlocking Shadowed Password Database

Before we end the shadowed password section, there are two additional functions that may be used for shadowed passwords. They are as follows:

#include <shadow.h>

int lckpwdf();
int ulckpwdf();


It's good practice to lock files (exclusively) when using them in public areas. For example, if the file is located in /tmp or in our case /etc. There may be an open administration tool somewhere working on these system critical files, and the last thing you need is a race condition or corrupted file. The shadow password suite gives us two functions lckpwdf, and ulckpwdf. These functions attempt to lock and unlock, respectively, the shadowed password database, and the password database.

After issuing the command, the lckpwdf function will try to lock /etc/shadow and /etc/password for up to 15 seconds. If lckpwdf fails to attain an exclusive lock on either one of these files, then the function returns a negative number. You should programmatically take care of this error to inform you of this fact. When you are done updating the shadow password file, it is good practice to unlock the files by issuing ulckpwdf.

Sample Programs

Listing all groups

Name: ex2-5.c Covers: getgrent

#include <stdio.h>
#include <grp.h>
#include <sys/types.h>

int main()
{
struct group *g_entry;
int i;

while( (g_entry=getgrent()) != 0 ) {
printf("%sPasswd: %s
"Group Id: %ld Members:
g_entry->gr_name,


g_entry->gr_passwd,
g_entry->gr_gid);

for (i=0;g_entry->gr_mem[i]!=0;++i) {
printf("%s ",g_entry->gr_mem[i]);
}
printf("
}

return(0);
}

Getting by name and by id

Name: ex2-6.c Covers: getgrgid, getgrnam

#include <stdio.h>
#include <grp.h>
#include <sys/types.h>
#include <errno.h>

int main()
{
struct group *g_entry1, *g_entry2;
int i;

/* We are getting the group entry for OUR own group id,
we get our own group id by *remember* getgid()!! =) */
g_entry1 = getgrgid(getgid());

printf("%sPasswd: %s
"Group Id: %ld Members:g_entry1->gr_name,
g_entry1->gr_passwd, g_entry1->gr_gid);

for (i=0;g_entry1->gr_mem[i]!=0;++i)
{ printf("%s ",g_entry1->gr_mem[i]);}
printf("

/* Now, let's get the same data, but based off of the
group name that we just got (gr_name) */
g_entry2 = getgrnam(g_entry1->gr_name);
printf("*******%sPasswd: %s
"Group Id: %ld Members:g_entry2->gr_name,
g_entry2->gr_passwd, g_entry2->gr_gid);

for (i=0;g_entry2->gr_mem[i]!=0;++i)
{ printf("%s ",g_entry2->gr_mem[i]); }
printf("

return 0;
}

Writing our own putgrent

Name: ex2-7.c Covers: fgetgrent, (home-brew) putgrent

#include <stdio.h>
#include <grp.h>
#include <sys/types.h>
#include <errno.h>

int putgrent(const struct group *g, FILE *stream);

int main()
{
struct group *mygroup, *mygroup2;
FILE *myfile;
myfile = fopen("mygroup.db","w");

mygroup = getgrgid(getgid());

/* remember this putgrent is our own homemade
function. You may want to save this somewhere! =) */
putgrent(mygroup,myfile);

fclose(myfile);
myfile = fopen("mygroup.db","r");

mygroup2 = fgetgrent(myfile);

printf("The first group in our home-brew file is called %s
mygroup2->gr_name);

fclose(myfile);

return 0;
}


int putgrent(const struct group *g, FILE *stream)
/* This function mimics the behaviour demonstrated by
putpwent() except that it operates on groups instead */
{

int temp_length=0;
int i=0;
char temp_double[27]; /* holds the string of gid */
char *temp_holder; /* create a temporary holder
for our output data */

if (g==NULL || stream==NULL) {
return -1;
}

/* first thing we want to do is calculate
how long our line's going to be. We do
this by getting the strlen() of each of
the group struct's members */

temp_length=strlen(g->gr_name);
temp_length+=strlen(g->gr_passwd);

/* now in order to get the length of the string
version of the group ID, we have to convert it
then count the characters */

sprintf(temp_double, "%ld", g->gr_gid);
temp_length+=strlen(temp_double);

/* now we have to iterate through the loop to
get our member's length. */

for(i=0;g->gr_mem[i]!=0;++i) {
temp_length+=strlen(g->gr_mem[i]);
}

/* Cool! We got the length of the line, let's
add `i' to it because we are going to need
to put coma's in there between the names,
and add 10 for extra colons, and padding! */

temp_length+=(10+i);
temp_holder = (char *)calloc(temp_length,sizeof(char));

if ( temp_holder == NULL ) {
return -1;
}

sprintf(temp_holder,"%s:%s:%ld:",
g->gr_name, g->gr_passwd, g->gr_gid);

for(i=0;g->gr_mem[i]!=0;++i) {
strcat(temp_holder,g->gr_mem[i]);
strcat(temp_holder,",");
}

/* And finally remove the extra coma, replace it with
a newline! */
temp_holder[strlen(temp_holder)-1] = '

/* write the whole line to a file handed to you by
stream */

if ( fputs(temp_holder, stream) < 0 ) {
return -1;
}


}



Elmo Recio is the sysadmin section's newest writer, and he can be E-mailed at polywog@navpoint.com.




   Page 1 of 1