glibc 用户数据库

最后更新: 2021-02-08 07:06:54 +0800 +0800
tags: Glibc | Linux | Users
原文以英文撰写。您正在阅读AI翻译版本。

用户数据库将登录名和数字用户 ID 映射到账户记录。/etc/passwd 是常见表示, 但应用不能假设该文件是权威来源。glibc 通过名称服务切换(NSS)解析 passwd 数据库; /etc/nsswitch.conf 中的 passwd: 条目可以查询本地文件、systemd、LDAP、NIS 或其他已安装提供程序。

PAM 不执行这些查询。PAM 处理身份验证和账户管理策略,而 NSS 回答身份数据库查询。

账户记录 #

<pwd.h> 定义公共记录:

c
struct passwd {
    char  *pw_name;    /* login name */
    char  *pw_passwd;  /* password field, commonly "x" */
    uid_t  pw_uid;     /* user ID */
    gid_t  pw_gid;     /* primary group ID */
    char  *pw_gecos;   /* user information */
    char  *pw_dir;     /* home directory */
    char  *pw_shell;   /* login shell */
};

在普通 Linux 安装中,历史上的密码字段不再暴露可复用的密码哈希。 哈希保存在单独保护的 shadow 数据库中;公共 passwd 记录通常包含 x 或其他标记。

查找用户 #

两个函数执行键控查询:

c
#include <pwd.h>

struct passwd *getpwnam(const char *name);
struct passwd *getpwuid(uid_t uid);

getpwnam() 按登录名搜索,getpwuid() 按数字 UID 搜索。 成功时,两者都返回指向库管理存储的指针。后续的密码数据库调用可能覆盖该存储, 应用不能释放它。NULL 既可能表示“未找到”,也可能表示错误; 在调用前将 errno 设为零,并在区别重要时调用后检查它。

NSS 定义查询语义。请求可能导致访问本地文件、IPC 或网络,NSS 策略还可以在每个提供程序返回结果后决定停止或继续。 因此,直接解析 /etc/passwd 不等同于调用这些接口。

使用可重入接口 #

多线程代码应使用 getpwnam_r() 和 getpwuid_r(),因为它们将字符串放入调用方提供的存储:

c
struct passwd pwd;
struct passwd *result;
char *buf = malloc(buflen);

int error = getpwnam_r("build", &pwd, buf, buflen, &result);
if (error != 0) {
    /* error is an error number; ERANGE means the buffer was too small */
} else if (result == NULL) {
    /* no matching account */
} else {
    printf("%s has UID %ju\n", pwd.pw_name, (uintmax_t)pwd.pw_uid);
}

查询完成时函数返回零,包括未找到的情况。成功时 *result 指向提供的 struct passwd, 没有匹配记录时为 NULL。正错误号直接报告失败,而不是通过返回值 -1 报告。 如果字符缓冲区无法容纳所有被引用的字符串,函数返回 ERANGE;增大缓冲区后重试。 sysconf() 返回的 _SC_GETPW_R_SIZE_MAX 是初始大小提示;返回 -1 时,它不是保证的上限。

pwd 内的返回指针指向 buf,因此使用记录时两个对象都必须保持有效。 可重入性保护库存储,但不会使 NSS 提供程序的外部数据库不可变,也不会使一系列独立查询具有原子性。

枚举数据库 #

流式 API 按配置提供程序暴露的顺序访问账户记录:

c
setpwent();

struct passwd *entry;
while ((entry = getpwent()) != NULL)
    printf("%s\n", entry->pw_name);

endpwent();
  • setpwent() 将枚举倒回开头,并可能打开提供程序资源。
  • getpwent() 使用共享内部存储返回下一项。
  • endpwent() 关闭枚举资源。

这些函数维护进程全局枚举状态,多个线程不能安全地同时使用。 getpwent_r() 提供调用方拥有的记录和字符串缓冲区,但枚举仍有共享流状态; 当另一个线程操作该状态时,glibc 将 setpwent()、getpwent_r() 和 endpwent() 分类为不安全。 如果其他线程可能同时枚举,应将完整枚举串行化。

网络后端目录的枚举也可能不完整或成本高昂。应用只需要一个身份时使用键控查询, 不要将完整扫描视为稳定快照。

参考资料 #

评论

在 GitHub 上阅读或评论(发表评论需要登录)