在Linux中,每个用户都有一个唯一的用户名和用户id。而用户又可以隶属于一个或多个组,每个组也有一个唯一的组名和组id。
1. 密码文件/etc/passwd
首先来看密码文件,对系统中的每个用户账户,密码文件/etc/passwd都会专门有一行对其进行描述,每一行都包含7个字段,用冒号分割,例如:
经过加密的密码:该字段是经过加密处理的密码,这里用的字母'x'来显示。
用户id:用户id,对于root用户,用户id为0。
组id:组id。
注释:存放关于用户的描述性文字。
主目录:用户工作主目录,可以查看HOME环境变量。
登录shell:登录的shell,例如/bin/bash。
对于密码文件的访问,可以通过getpwnam、getpwuid来操作,原型如下:
2. 组文件/etc/group
系统中每个组在/etc/group中都有一条记录,每个记录包含4个字段,用冒号分割,例如:
经过加密的组密码:组密码属于非强制性的。
组id:组id号。
用户列表,属于该组的用户列表,之间用逗号分割。
对于root组后面没有用户列表,但是root用户确实属于root组,因为它的组id为0。
对于组文件的访问也有两个函数,原型如下:
root组的members成员为空的。
既然这里提到了组的概念,那么我们怎么查看用户属于哪个组呢,linux提供了一个命令,那就是groups命令。groups命令会将用户所属组给列举出来,如果没有指定用户名,那么显示的就是当前登录的用户。
3. 密码文件/etc/shadow
经过加密处理的密码实际上是存储在/etc/shadow文件中的,所以在/etc/passwd文件中看到的用户密码使用字符'x'来表示的。另外/etc/shadow文件是需要具有root用户权限才能访问的,普通用户是没有权限访问的。
对于/etc/shadow文件的访问可以通过getspnam函数,原型如下:
得到的struct spwd结构指针类型定义如下:
接下来我们用一个实例来看如何通过用户名和密码的方式来认证。
由于安全方面的原因,Linux系统采用的单向加密算法对密码进行加密,也就是说通过加密密码是无法还原出原始密码的。因此,认证的方式只有通过原始密码,然后采用同一加密算法进行加密,并将得到的加密密码与/etc/shadow中的密码进行匹配,完全相同,才表示认证成功。加密算法使用crypt函数,原型如下:
1. 密码文件/etc/passwd
首先来看密码文件,对系统中的每个用户账户,密码文件/etc/passwd都会专门有一行对其进行描述,每一行都包含7个字段,用冒号分割,例如:
root:x:0:0:root:/root:/bin/bash登录名:第一个字段是登录名或者说是用户名。
经过加密的密码:该字段是经过加密处理的密码,这里用的字母'x'来显示。
用户id:用户id,对于root用户,用户id为0。
组id:组id。
注释:存放关于用户的描述性文字。
主目录:用户工作主目录,可以查看HOME环境变量。
登录shell:登录的shell,例如/bin/bash。
对于密码文件的访问,可以通过getpwnam、getpwuid来操作,原型如下:
#include <sys/types.h>
#include <pwd.h>
struct passwd *getpwnam(const char *name);
struct passwd *getpwuid(uid_t uid);
而passwd结构定义如下:
struct passwd {
char *pw_name; /* username */
char *pw_passwd; /* user password */
uid_t pw_uid; /* user ID */
gid_t pw_gid; /* group ID */
char *pw_gecos; /* user information */
char *pw_dir; /* home directory */
char *pw_shell; /* shell program */
};
其中每个成员对应/etc/passwd中的一个字段,getpwnam和getpwuid返回值都是一样的,只是传递参数不一样,一个是用户名,而另一个是用户id。代码示例如下:#include <stdio.h>
#include <pwd.h>
int main(int argc, char *argv[])
{
struct passwd *pwd = NULL;
pwd = getpwnam(argv[1]);
if (pwd == NULL) {
perror("getpwnam");
return -1;
}
printf( "name \t\t%s\n"
"passwd \t\t%s\n"
"uid \t\t%d\n"
"gid \t\t%d\n"
"information \t%s\n"
"directory \t%s\n"
"shell \t\t%s\n", pwd->pw_name, pwd->pw_passwd,
pwd->pw_uid, pwd->pw_gid,
pwd->pw_gecos, pwd->pw_dir, pwd->pw_shell);
return 0;
}
得到root用户信息如下:name root passwd x uid 0 gid 0 information root directory /root shell /bin/bash
2. 组文件/etc/group
系统中每个组在/etc/group中都有一条记录,每个记录包含4个字段,用冒号分割,例如:
root:x:0:组名:组名,与用户登录名类似。
经过加密的组密码:组密码属于非强制性的。
组id:组id号。
用户列表,属于该组的用户列表,之间用逗号分割。
对于root组后面没有用户列表,但是root用户确实属于root组,因为它的组id为0。
对于组文件的访问也有两个函数,原型如下:
#include <sys/types.h>
#include <grp.h>
struct group *getgrnam(const char *name);
struct group *getgrgid(gid_t gid);
返回值为struct group类型指针,struct group定义如下:struct group {
char *gr_name; /* group name */
char *gr_passwd; /* group password */
gid_t gr_gid; /* group ID */
char **gr_mem; /* group members */
};
代码示例如下:#include <stdio.h>
#include <grp.h>
int main(int argc, char *argv[])
{
struct group *grp = NULL;
int i;
grp = getgrnam(argv[1]);
if (grp == NULL) {
perror("getgrnam");
return -1;
}
printf( "name \t\t%s\n"
"passwd \t\t%s\n"
"gid \t\t%d\n", grp->gr_name,
grp->gr_passwd, grp->gr_gid);
printf("members \t");
for (i = 0; grp->gr_mem[i] != NULL; i++) {
printf("%s ", grp->gr_mem[i]);
}
printf("\n");
return 0;
}
root组信息如下:name root passwd x gid 0 members
root组的members成员为空的。
既然这里提到了组的概念,那么我们怎么查看用户属于哪个组呢,linux提供了一个命令,那就是groups命令。groups命令会将用户所属组给列举出来,如果没有指定用户名,那么显示的就是当前登录的用户。
3. 密码文件/etc/shadow
经过加密处理的密码实际上是存储在/etc/shadow文件中的,所以在/etc/passwd文件中看到的用户密码使用字符'x'来表示的。另外/etc/shadow文件是需要具有root用户权限才能访问的,普通用户是没有权限访问的。
对于/etc/shadow文件的访问可以通过getspnam函数,原型如下:
#include <shadow.h>
struct spwd *getspnam(const char *name);
注意:需要root权限。得到的struct spwd结构指针类型定义如下:
struct spwd {
char *sp_namp; /* Login name */
char *sp_pwdp; /* Encrypted password */
long sp_lstchg; /* Date of last change (measured
in days since 1970-01-01 00:00:00 +0000 (UTC)) */
long sp_min; /* Min # of days between changes */
long sp_max; /* Max # of days between changes */
long sp_warn; /* # of days before password expires
to warn user to change it */
long sp_inact; /* # of days after password expires
until account is disabled */
long sp_expire; /* Date when account expires (measured
in days since 1970-01-01 00:00:00 +0000 (UTC)) */
unsigned long sp_flag; /* Reserved */
};
注意:得到的密码也是经过加密处理的密码。接下来我们用一个实例来看如何通过用户名和密码的方式来认证。
由于安全方面的原因,Linux系统采用的单向加密算法对密码进行加密,也就是说通过加密密码是无法还原出原始密码的。因此,认证的方式只有通过原始密码,然后采用同一加密算法进行加密,并将得到的加密密码与/etc/shadow中的密码进行匹配,完全相同,才表示认证成功。加密算法使用crypt函数,原型如下:
#define _XOPEN_SOURCE /* See feature_test_macros(7) */
#include <unistd.h>
char *crypt(const char *key, const char *salt);
代码示例如下:#define _XOPEN_SOURCE
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <pwd.h>
#include <shadow.h>
int main(void)
{
char *username, *password, *encrypted, *p;
struct passwd *pwd;
struct spwd *spwd;
size_t len;
int authok;
long lnmax;
lnmax = sysconf(_SC_LOGIN_NAME_MAX);
if (lnmax < 0) /* If limit is indeterminate */
lnmax = 256; /* make a guess */
username = malloc(lnmax);
if (username == NULL)
exit(EXIT_FAILURE);
printf("Username: ");
fflush(stdout);
if (fgets(username, lnmax, stdin) == NULL)
exit(EXIT_FAILURE); /* Exit on EOF */
len = strlen(username);
if (username[len - 1] == '\n')
username[len - 1] = '\0'; /* Remove trailing '\n' */
pwd = getpwnam(username);
if (pwd == NULL) {
printf("couldn't get password record\n");
exit(EXIT_FAILURE);
}
spwd = getspnam(username);
if (spwd == NULL && errno == EACCES) {
printf("no permission to read shadow password file\n");
exit(EXIT_FAILURE);
}
if (spwd != NULL) /* If there is a shadow password record */
pwd->pw_passwd = spwd->sp_pwdp; /* Use the shadow password */
password = getpass("Password: ");
/* Encrypt password and erase cleartext version immediately */
encrypted = crypt(password, pwd->pw_passwd);
for (p = password; *p != '\0'; )
*p++ = '\0';
if (encrypted == NULL)
exit(EXIT_FAILURE);
authok = strcmp(encrypted, pwd->pw_passwd) == 0;
if (!authok) {
printf("Incorrect password\n");
exit(EXIT_FAILURE);
}
printf("Successfully authenticated: UID=%ld\n", (long) pwd->pw_uid);
/* Now do authenticated work... */
exit(EXIT_SUCCESS);
}
注意使用crypt函数时需要连接crypt库,所以在练级时需要加上-lcrypt,编译上面代码命令如下:
gcc -Wall -o test check.c -lcrypt
参考教程:The Linux Programming Interface - A Linux and UNIX System Programming Handbook.pdf