[Home] [Credit Search] [Category Browser] [Staff Roll Call] | The LINUX.COM Article Archive |
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>
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>
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>
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
/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 {
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>
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:
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> 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>
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> 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.
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>
Putting and Getting a record from a custom file Name: ex2-3.c Covers: fgetpwent, putpwent
#include <stdio.h>
Getting a record by name, then by user id. Name: ex2-4.c Covers: getpwnam, getpwuid
#include <stdio.h>
/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: 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.)
231struct group *g_entry; Getting Groups
The group system calls for manipulating the group database are as follows:
#include <grp.h> 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> 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>
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> 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.
#include <shadow.h> 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.
#include <shadow.h> 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> 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> Getting by name and by id
Name: ex2-6.c Covers: getgrgid, getgrnam
#include <stdio.h> Writing our own putgrent
Name: ex2-7.c Covers: fgetgrent, (home-brew) putgrent
#include <stdio.h>
| |
Page 1 of 1 |