Passwd
The passwd is program on Unix systems to manage users’ passwords. The user and password information on most Unix systems is stored in two separate files: /etc/passwd for user information, and /etc/shadow for password information, including encrypted password value, expiration data, UID, GID, and etc… The rationale behind storing information in separate files is discussed in Why shadow your passwd file?
The passwd program is a part of the shadow-utils, which includes a series of programs to manage user accounts, group accounts, and converting plain passwords to shadow password format, such as: groupadd, useradd, usermod, login, passwd, su, and etc…
The source file of passwd
is available in Debian Alioth Page and Debian Package Information Page. I downloaded the 4.1.5 original source for my study. See opensource isn’t just a campaign slogan, it’s something real!
Basically, what passwd does is to manage passwords, like updating passwords, setting minimum and maximum password expiration date, and all these information is saved to the /etc/passwd and /etc/shadow files, and password information in particular, needs an encryption library for protection, instead of being saved as plain text.
Below is a NON-comprehensive list of code files required by the passwd program in the shadow-utils code base:
src/passwd.c lib/defines.h lib/getdef.(h/c) lib/shadow.(h/c) lib/shadowio.(h/c) lib/commonio.(h/c) lib/sgetspent.c
How we change our password
Data structure definitions first?
- Shadow passwd struct: Defined in shadow.h in linux include directory. Defines the structure of the shadow file. The pointer in the main function is defined as:
1 | const struct spwd *sp; /* Shadow file entry for user */ |
- Passwd Structure:
Defined in pwd.h in linux include directory. Defines the
/etc/passwd
file structure. The pointer in the main function is defined:
1 | const struct passwd *pw; /* Password file entry for user */ |
-
Shadow_db structure: Defined in
lib/shadowio.c
. It’s a doubly linked list, storing information for all the shadow file entries. It’s declared with a type calledstruct commonio_db
, defined incommonio.h
.Shadow library has
commonio.c
for all the common io data structures and operations, andshadowio.c
, which could be seen as a wrapper around common io for all shadow file data structure and operations.The
shadow_db
defined as below. TheSHADOW_FILE
as you might have already guessed, is a macro defined as“/etc/shadow”
.
1 | static struct commonio_db shadow_db = { |
- Some important global variables
1 | static char *name; /* The name of user whose password is being changed */ |
Main function
Though the whole password update procedure could be simply described as “reading and updating the /etc/passwd
and /etc/shadow
file”, the shadow library uses piles of code to check identity, permission, and several layers of function calls for encryption, and finally updating files. It needs to consider every aspect of the problem, which makes the code size larger than you might expect.
Also, passwd libray took PAM, TCB and SELinux into considerations. I would skip these here for I don’t yet have time to study all.
A good place to start reading is the main()
entry of the passwd.c
. The procedures could be summarized as follows:
- Initialization:
Init data structures (
const struct passwd *pw
,const struct spwd *sp
, etc.), sanitize environment, check if the user is root, …; - Parse parameters: A large switch case for all parameters. As I’m now only interested in updating my password, I would follow the execution path where no parameters are given;
- Get username, check permissions:
1 | pw = get_my_pwent(); |
Get username, init pw and sp data structures, check if the user is root or if the user is trying to change his own password. Then it checks the validity of the user’s account: is it expired, is its min password change time reached? These are in check_password()
function.
- Get new password:
1 | if (new_password(pw) != 0){...} |
Here’s where there’s most fun. It’ when the passwd
program prompts you for your old password, and tell you to input your new password. If you fail in trying too many times, the program would get upset and refuses to update password for you.
Under the hood, it also does the following things:
-
Encrypt your input with
pw_encrypt()
(defined inlib/encrypt.c
), then compare it with the old encrypted string. There must be a lot of fun to dig into the encryption method, but it’s not in the scope of this blog; -
Warns you of weak password;
-
Encrypt the password then immediately wipe the cleartext password, saves the encrypted password to the global variable
crypt_passwd
, which would then copied to other data structures, and then saves to the shadow file. -
Update shadow file:
1 | update_shadow(); |
The program warns you the username you are changing password, then it calls the update_shadow()
if you have shadow file. Otherwise, it calls update_noshadow()
.
The update_shadow()
is going to the core of the program, and it’s what I will observe closely.
update_shadow() function
Function update_shadow()
is defined in src/passwd.c
, and the summary of the procedures is:
- Set a global lock:
1 | if (spw_lock() == 0){...} |
Lock the shadow password file access. Spit an error if it’s already locked.
- Open the shadow file:
1 | if (spw_open(O_RDWR)==0){...} |
Taking a deeper look inside the spw_open
in lib/shadowio.c
, you could find that here is when it opens up the shadow file, reads it, and stores all the entries to the shadow_db
doubly linked list.
- Locate the entry by name:
1 | sp = spw_locate (name); |
Also a function call in lib/shadowio.c
. The name
param is the current username.
- Create nsp Data Structure:
1 | nsp = __spw_dup (sp); |
It copies the content in sp to a new pointer nsp;
- Update the encrypted passwd:
1 | update_crypt_pw(nsp->sp_pwdp); |
Finally! The crypted_passwd
is copied to the data structure, with:
1 | cp=xstrdup(crypt_passwd) |
inside of update_crypt_pw()
function. This process is hidden so deep.
The nsp data structure would then carry this encrypted password to the shadow file. The program would also update metadata, such as the expiration date and so on;
- Update the shadow_db Data Structure:
1 | if (spw_update(nsp) == 0){ |
The spw_update()
, again is a wrapper for the relating commonio_update()
. Inside it would try to find the entry of the shadow_db
data structure, or create new entry when not found. Then it saves all the information in the sp
to the shadow_db
.
- Close the shadow file, unlock the global lock: *```c spw_close(); … spw_unlock();
Close and saves the shadow file to its place. Unlock the global lock, concludes the whole process. Still, there's much interesting things to look at inside the <code>spw_close()</code> and <code>commonio_close()</code>, but I think I've written long enough.
## Afterthoughts
Reading code is fun, recording the whole process is even more so. It's a rewarding process, especially for some high-quality code as shadow library. It kinda teaches you how top-notch programmers tackles system-level problems. It's also tiring though, when you dig into all the function calls, variables (especially global variables) while tracking its execution path. At some point I really wish the code could be a little bit more commented.
I might have the energy to blog all the code I will read, but I think I will definitely read more code before I start writing something similar. To conclude, it's actually fun experience that quenches your curiosity of "How it actually works".
> Written with [StackEdit](https://stackedit.io/).