- 每个用户都有一个唯一的用户ID,且每个用户可以属于多个组;每个组都有唯一一个名称和一个组ID
- 用户和组ID是为了确定各种系统资源的使用权以及对进程访问资源的权限加以控制
- 针对系统的每一个用户账号,系统密码文件/etc/passwd会有一行记录与其对应,每行都包含7个字段,其间用冒号分隔,比如
sone:x:1000:1000:pty,,,:/home/sone:/bin/bash
,各个字段定义如下
- 用户名字段
- 经过加密的密码,通常如果启用了shadow密码,那么该字段为x,真正加密的密码保存于shadow文件中
- 用户ID
- 首选属组的组ID
- 注释
- 该用户的主目录
- 登录shell
- 相对的,每一个group在/etc/group文件中也有记录,每条记录包含4个字段,以冒号分隔,比如
sone:x:1000:
,四个字段的定义如下
- 组名
- 经过加密的密码,同用户记录一样,通常为x
- 组ID
- 用户列表,表示属于该组的用户名列表,其中以逗号分隔
相关函数
- 与/etc/passwd相关的函数集原型以及结构体如下
#include <sys/types.h>
#include <pwd.h>
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 */
};
//根据用户名获得相关的记录,如果在文件中找不到记录返回NULL
struct passwd *getpwnam(const char *name);
//根据用户ID获得相关记录,找不到的话返回NULL
struct passwd *getpwuid(uid_t uid);
/*以下三个函数是用来扫描passwd文件中所有记录的*/
struct passwd *getpwent(void);
void setpwent(void);
void endpwent(void);
- 如果想要扫描/etc/passwd文件中的记录,那么首先调用getpwent函数获得指向第一条记录的指针,然后该函数自动将指针指向下一条记录;当扫描完以后调用endpwent将其关闭;另外可以调用endpwent函数将指针重新指向文件的第一条记录
- 与/etc/group相关的函数集原型以及结构体如下,功能和passwd文件的相关函数类似
#include <sys/types.h>
#include <grp.h>
struct group {
char *gr_name; /* group name */
char *gr_passwd; /* group password */
gid_t gr_gid; /* group ID */
char **gr_mem; /* NULL-terminated array of pointers
to names of group members */
};
struct group *getgrnam(const char *name);
struct group *getgrgid(gid_t gid);
struct group *getgrent(void);
void setgrent(void);
void endgrent(void);
#include <shadow.h>
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 */
};
//根据用户名查找相应的密码记录
struct spwd *getspnam(const char *name);
//遍历处理函数
struct spwd *getspent(void);
void setspent(void);
void endspent(void);
#include <unistd.h>
char* crypt(const char* key,const char* salt);
- crypt算法会接受一个最长可达8字符的密码,并对其进行加密,若加密成功返回指向该密文的指针;salt参数是一个指向两个字符长度的字符串,用来改变加密算法;一般,会将/etc/shadow对应的密码密文传进去,该函数只会截取前两个字符,只有这样才能够对候选密码进行验证。在编译该函数时需要开启-lcrypt选项,指定链接库
- 在需要用户输入密码时,常常调用getpass函数,可以屏蔽回显;该函数会打印提示信息prompt,并返回输入的密码,原型如下
#include <unistd.h>
char *getpass(const char *prompt);
进程凭证
- 每个进程都有一套用户和组ID,分别是实际用户和组ID,有效用户和组ID,设置用户和组ID,文件系统用户和组ID(linux专有)以及辅助组ID
- 一般在登录系统时,登录shell会从/etc/passwd文件中读取有关当前登录用户的用户ID和组ID,然后将shell进程的实际用户ID和实际组ID设置为查询到的用户ID和组ID;并且后续的子进程会继承父进程的实际用户和组ID
- 进程的有效用户和组ID决定当前进程是否有权限访问系统资源,如文件或者IPC对象;有效用户ID为0的进程被称为特权进程,某些系统调用只能由特权进程执行;改变一个进程的有效ID的方法有两种,其一是调用相关的系统调用,其二是通过设置用户和组ID
- 设置用户和组ID(set-user-ID和set-group-ID)可以将进程的有效用户和组ID置为可执行文件的实际用户和组ID;例如,对于一个可执行文件来说,如果设置了设置用户ID权限位并且该文件的实际用户ID为root,那么普通用户在执行该文件时会具有root用户的所有权限,因为当前进程的有效用户ID会被设置为root;比如说linux系统的/bin目录下有一个passwd可执行程序,用来更改密码,它的权限位为"-rwsr-xr-x",而正常情况下同组用户和其他用户对于shadow文件的修改没有写权限,但是因为它设置了设置用户ID位,则当执行passwd的时候,执行进程的有效用户ID被设置为root,进而可以对shadow文件进行修改
- 与设置用户和组ID相关的还有一组ID叫做保存set-user-ID和保存set-group-ID,保存设置用户和组ID由有效用户和组ID复制而来,为了方便进程切换有效用户和组ID
- 文件系统用户和组ID一般都和有效用户和组ID等价,除非调用特定的系统调用去改变,一般不作考虑
- 辅助组ID记录进程所属的若干附加的组,新进程从父进程继承这些ID
- 获取用户和组相关ID的函数原型如下,getuid和getgid分别是获取当前进程的实际用户ID和实际组ID;geteuid和getegid分别获取当前进程的有效用户ID和有效组ID
#include <unistd.h>
#include <sys/types.h>
uid_t getuid(void);
uid_t geteuid(void);
gid_t getgid(void);
gid_t getegid(void);
- 修改有效用户和组ID的函数如下,对于非特权进程来讲,setuid只能将进程的有效ID设置为实际用户ID或者保存set-user-ID,如果设置为其他值,调用出错返回-1;对于特权进程来讲,setuid可以将有效ID,实际ID和保存设置ID设置为任意值,但是会丢失所有特权,且不可逆
#include <sys/types.h>
#include <unistd.h>
int setuid(uid_t uid);
int setgid(gid_t gid);
- 如果想要防止不可逆的情况,可以使用下面一组函数,对于非特权进程来说调用的规则和上述函数类似,而对于特权进程来说,seteuid只修改有效用户ID为任意值,这意味着在将有效用户ID改为其他值以后可以重新将有效用户ID设置为0
#include <sys/types.h>
#include <unistd.h>
int seteuid(uid_t euid);
int setegid(gid_t egid);
- 在linux中,有一组专用的对于查看和修改各种ID信息的函数,调用规则与上述类似,只是更加方便,原型如下
#define _GNU_SOURCE /* See feature_test_macros(7) */
#include <unistd.h>
int getresuid(uid_t *ruid, uid_t *euid, uid_t *suid);
int getresgid(gid_t *rgid, gid_t *egid, gid_t *sgid);
int setresuid(uid_t ruid, uid_t euid, uid_t suid);
int setresgid(gid_t rgid, gid_t egid, gid_t sgid);
网友评论