第8章 用户和组
口令文件:/etc/passwd
用户名:加密口令:用户ID:组ID:注释字段:初始工作目录:初始shell
- 通常有一个用户名为root,用户ID为0的登录项(超级用户)
- 加密口令字段占位,包含x表示内容存储在了shadow密码文件
- 某些字段可能为空,如果加密口令字段为空,意味着该用户没有口令
- shell字段包含一个可执行程序,用作登陆shell;为空,则取系统默认,如果是/dev/null,标明是一个设备,不是可执行文件,任何人无法以该用户登陆
- 为了阻止一个特定用户登陆,除了/dev/null外,还可以将/bin/false作为登陆shell,或/bin/true
- 使用nobody用户名的目的使任何人都可以登陆,只能访问人人皆可读写的文件
- 使用finger指令支持注释字段的附加信息 ``` struct passwd { char pw_name; / Username / char pw_passwd; / Password / uid_t pw_uid; / User ID / gid_t pw_gid; / Group ID / char pw_gecos; / Real Name or Comment field / char pw_dir; / Home directory / char pw_shell; / Shell Program */ };
从口令文件中获取记录:
include
struct passwd getpwnam(const char name); struct passwd *getpwuid(uid_t uid); // 返回值:若成功,返回一个指向一条记录的指针,若出错,返回NULL // 当未启用shadow密码的情况下,pw_passwd才会包含有效信息
对于出错和“未发现匹配记录”需要加以区分:
struct passwd *pwd;
errno = 0; pwd = getpwnam(name);
if (NULL == pwd) { if (0 == errno) // not found; else // error }
按顺序扫描口令文件中的记录:
include
struct passwd getpwdent(void); // 两个函数,若成功,返回指针,若出错或到达文件尾,返回NULL
void setpwent(void); // 重返文件的起始处 void endpwent(void);
如下代码遍历整个密码文件,打印出登录名和用户ID:
struct passwd *pwd;
while ((pwd = getpwent()) != NULL) printf(“”%-8s %5ld\n”, pwd->pw_name, (long)pwd->pw_uid); endpwent();
##### 阴影文件:/etc/shadow
包含登录名、经过加密的密码、若干与安全性相关的字段,仅有几个用户ID为root的程序如login和passwd才可以访问
struct spwd { char sp_namp; / Login name / char sp_pwdp; / Encrypted password / long int sp_lstchg; / Date of last change / long int sp_min; / Minimum number of days between changes / long int sp_max; / Maximum number of days between changes / long int sp_warn; / Number of days to warn user to change the password / long int sp_inact; / Number of days the account may be inactive / long int sp_expire; / Number of days since 1970-01-01 until account expires / unsigned long int sp_flag; / Reserved / };
这组函数与口令文件的一组函数对应:
include
struct spwd getspnam(const char name); struct spwd getspent(void); // 两个函数,若成功,返回指针,若出错或到达文件尾,返回NULL
void setspent(void); void endspent(void);
##### 组文件:/etc/group
系统中每个组在组文件中对应一条记录,包含四个字段:
- 组名
- 经过加密的密码,现代UNIX系统很少使用,存放于/etc/gshadow中,仅供由特权的用户和程序访问
- 组ID
- 用户列表
为了证明用户avr是users、staff、teach三个组的成员,首先在口令文件中有记录:
avr:x:1001:100:Anthony Robins:/home/avr:/bin/bash
其中组ID是100,用户名是avr,则在组文件中应有如下记录:
users:x:100: staff:x:101:mtk,avr,martinl teach:x:104:avr,alc
```
struct group
{
char *gr_name; /* group name */
char *gr_passwd; /* encrypted password if not password shadowing */
gid_t gr_gid; /* group id */
char ** gr_mem; /* NULL-terminated array of pointers to names of members listed in /etc/group */
}
查看组名或组ID:
#include<grp.h>
struct group *getgrpid(gid_t gid);
struct group *getgrnam(const char *name);
// 两个函数,若成功,返回指针,若出错,返回NULL
如果要搜索整个组文件:
#include<grp.h>
struct group *getgrent();
// 若成功,返回指针,若出错或到达文件尾,返回NULL
void setgrent();
void endgrent();
密码加密和用户认证
UNIX采用单向加密对密码进行加密,所以由密码的加密形式无法还原出原始密码,验证候选密码的唯一方法是使用同一算法对其加密,并将其与/etc/shadow的密码进行匹配,加密算法封装于crypt函数
#define _XOPEN_SOURCE
#include <unistd.h>
char *crypt(const char *key, const char *salt);
// 返回值:若成功,返回长度为13个字符的静态字符串(头两个字符是对salt的拷贝),若出错,返回NULL
// key最长为8字符,salt是2个字符,用来扰动(改变)DES算法
// 要使用需在编译时开启-lcrypt选项
// MD5是一种复杂的哈希函数,返回34个字符,两种方法殊途同归,对输入的密码既不可逆又难以破解
// 解密后的明文在使用后应立即从内存删除,防止恶意之徒读取内核转储文件以获取密码
getpass首先屏蔽回显功能,并停止对终端特殊字符的处理(一般是CTRL+C),然后打印出prompt所指向的字符串,读取一行输入,返回以NULL结尾的输入字符串(剥离尾部的换行符):
#define _BSD_SOURCE
#include <unistd.h>
char *getpass(const char *prompt);
// 返回值:若成功,返回输入密码的静态字符串,若出错,返回NULL