文章目录
1. 前言
The GNU C Library Reference Manual for version 2.35
2. 用户和组
Users and Groups
每个可以登录系统的用户都由一个称为用户 ID 的唯一编号标识。每个进程都有一个有效的用户 ID,说明它拥有哪个用户的访问权限。
出于访问控制目的,用户被分成组。每个进程都有一个或多个组 ID 值,这些值说明进程可以使用哪些组来访问文件。
进程的有效用户和组 ID 共同构成了它的角色。这决定了进程可以访问哪些文件。通常,进程从父进程继承其角色,但在特殊情况下,进程可以更改其角色,从而更改其访问权限。
系统中的每个文件也有一个用户 ID 和一个组 ID。访问控制通过将文件的用户和组 ID 与正在运行的进程的 ID 进行比较来工作。
系统保存所有注册用户的数据库,以及所有已定义组的数据库。您可以使用一些库函数来检查这些数据库。
2.1. 用户和组 ID
User and Group IDs
计算机系统上的每个用户帐户都由用户名(或登录名)和用户 ID 标识。通常,每个用户名都有一个唯一的用户 ID,但也可以有多个登录名具有相同的用户 ID。用户名和相应的用户 ID 存储在您可以访问的数据库中,如用户数据库中所述。
用户按组分类。每个用户名属于一个默认组,也可以属于任意数量的补充组。属于同一组的用户可以共享非该组成员的用户无法访问的资源(例如文件)。每个组都有一个组名和组 ID。有关如何查找有关组 ID 或组名称的信息,请参阅组数据库。
2.2. 进程的角色
The Persona of a Process
在任何时候,每个进程都有一个有效的用户ID、一个有效的组ID和一组补充组ID。这些 ID 确定进程的权限。它们统称为过程的角色,因为它们确定“它是谁”以用于访问控制。
您的登录 shell 以一个角色开始,该角色由您的用户 ID、您的默认组 ID 和您的补充组 ID(如果您在多个组中)组成。在正常情况下,您的所有其他进程都会继承这些值。
一个进程也有一个真实的用户 ID,用于标识创建该进程的用户,以及一个真实的组 ID,用于标识该用户的默认组。这些值在访问控制中不起作用,因此我们不认为它们是角色的一部分。但它们也很重要。
真实和有效的用户 ID 都可以在进程的生命周期内更改。请参阅为什么要更改进程的角色?。
有关进程的有效用户 ID 和组 ID 如何影响其访问文件的权限的详细信息,请参阅如何确定您对文件的访问权限。
进程的有效用户 ID 还控制使用 kill 函数发送信号的权限。请参阅向另一个进程发送信号。
最后,有很多操作只能由有效用户ID为零的进程执行。具有此用户 ID 的进程是特权进程。通常用户名 root 与用户 ID 0 相关联,但可能还有其他用户名与此 ID 相关联。
2.3. 为什么要改变进程的角色?
Why Change the Persona of a Process?
进程需要更改其用户和/或组 ID 的最明显情况是登录程序。当登录开始运行时,它的用户 ID 是 root。它的工作是启动一个 shell,其用户和组 ID 是登录用户的 ID。(要完全完成此操作,登录必须设置真实的用户和组 ID 以及它的角色。但这是一个特殊情况。)
更改角色的更常见情况是普通用户程序需要访问实际运行它的用户通常无法访问的资源。
例如,您可能有一个由您的程序控制的文件,但不应由其他用户直接读取或修改,因为它实现了某种锁定协议,或者因为您希望保留文件的完整性或隐私性。它包含的信息。这种受限访问可以通过让程序更改其有效用户或组 ID 以匹配资源的 ID 来实现。
因此,想象一个将分数保存在文件中的游戏程序。游戏程序本身需要能够更新这个文件,不管谁在运行它,但是如果用户可以在不通过游戏的情况下编写文件,他们可以给自己任何他们喜欢的分数。有些人认为这是不可取的,甚至是应受谴责的。可以通过创建一个新的用户 ID 和登录名(例如,游戏)来拥有分数文件,并使该文件只能由该用户写入来防止这种情况发生。然后,当游戏程序要更新这个文件时,它可以将其有效用户ID更改为游戏用户ID。实际上,程序必须采用游戏角色,才能写入分数文件。
2.4. 应用程序如何改变角色
How an Application Can Change Persona
更改进程角色的能力可能是无意侵犯隐私甚至故意滥用的根源。由于潜在的问题,改变角色仅限于特殊情况。
您不能随意将您的用户 ID 或组 ID 设置为您想要的任何内容;只有特权进程才能做到这一点。相反,程序更改其角色的正常方式是预先设置好更改为特定用户或组。这是文件访问模式的 setuid 和 setgid 位的功能。请参阅访问权限的模式位。
当可执行文件的 setuid 位打开时,执行该文件会为进程提供第三个用户 ID:文件用户 ID。此 ID 设置为文件的所有者 ID。然后系统将有效用户 ID 更改为文件用户 ID。真实用户 ID 保持原样。同样,如果 setgid 位打开,则为进程分配与文件组 ID 相同的文件组 ID,并将其有效组 ID 更改为文件组 ID。
如果一个进程有一个文件 ID(用户或组),那么它可以随时将其有效 ID 更改为其真实 ID,然后再更改回其文件 ID。程序使用此功能来放弃其特殊权限,除非它们确实需要它们。这使得他们不太可能被诱骗以他们的特权做一些不恰当的事情。
可移植性 注意:旧系统没有文件 ID。要确定系统是否具有此功能,您可以测试编译器定义_POSIX_SAVED_IDS。(在 POSIX 标准中,文件 ID 称为保存的 ID。)
有关文件模式和可访问性的更一般性讨论,请参阅文件属性。
2.5. 阅读进程的角色
Reading the Persona of a Process
以下是读取进程的用户和组 ID 的函数的详细描述,既真实又有效。要使用这些工具,您必须包含头文件 sys/types.h 和 unistd.h。
数据类型:uid_t
这是用于表示用户 ID 的整数数据类型。在 GNU C 库中,这是 unsigned int 的别名。
数据类型:gid_t
这是用于表示组 ID 的整数数据类型。在 GNU C 库中,这是 unsigned int 的别名。
函数:uid_t getuid (void)
Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.
getuid 函数返回进程的真实用户 ID。
函数:gid_t getgid (void)
Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.
getgid 函数返回进程的真实组 ID。
函数:uid_t geteuid (void)
Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.
geteuid 函数返回进程的有效用户 ID。
函数:gid_t getegid (void)
Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.
getegid 函数返回进程的有效组 ID。
函数:int getgroups (int count, gid_t *groups)
Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.
getgroups函数用于查询进程的补充组ID。这些组 ID 的数量最多存储在数组组中;该函数的返回值是实际存储的组 ID 数。如果 count 小于补充组 ID 的总数,则 getgroups 返回值 -1 并将 errno 设置为 EINVAL。
如果 count 为零,则 getgroups 只返回补充组 ID 的总数。在不支持补充组的系统上,这将始终为零。
以下是使用 getgroups 读取所有补充组 ID 的方法:
gid_t *
read_all_groups (void)
{
int ngroups = getgroups (0, NULL);
gid_t *groups
= (gid_t *) xmalloc (ngroups * sizeof (gid_t));
int val = getgroups (ngroups, groups);
if (val < 0)
{
free (groups);
return NULL;
}
return groups;
}
2.6. 设置用户 ID
Setting the User ID
本节介绍用于更改进程的用户 ID(真实和/或有效)的功能。要使用这些工具,您必须包含头文件 sys/types.h 和 unistd.h。
函数:int seteuid (uid_t neweuid)
Preliminary: | MT-Safe | AS-Unsafe lock | AC-Unsafe lock | See POSIX Safety Concepts.
此函数将进程的有效用户 ID 设置为 neweuid,前提是允许该进程更改其有效用户 ID。特权进程(有效用户 ID 为零)可以将其有效用户 ID 更改为任何合法值。具有文件用户 ID 的非特权进程可以将其有效用户 ID 更改为其真实用户 ID 或文件用户 ID。否则,进程可能根本不会更改其有效用户 ID。
seteuid 函数返回值 0 表示成功完成,返回值 -1 表示错误。为此函数定义了以下 errno 错误条件:
EINVAL
neweuid 参数的值无效。
EPERM
该进程可能不会更改为指定的 ID。
较旧的系统(没有 _POSIX_SAVED_IDS 功能的系统)没有此功能。
函数:int setuid (uid_t newuid)
Preliminary: | MT-Safe | AS-Unsafe lock | AC-Unsafe lock | See POSIX Safety Concepts.
如果调用进程具有特权,则此函数将进程的真实用户 ID 和有效用户 ID 都设置为 newuid。它还会删除进程的文件用户 ID(如果有)。newuid 可以是任何合法值。(一旦完成,就无法恢复旧的有效用户 ID。)
如果进程没有特权,并且系统支持 _POSIX_SAVED_IDS 功能,则此函数的行为类似于 seteuid。
返回值和错误条件与 seteuid 相同。
函数:int setreuid (uid_t ruid, uid_t euid)
Preliminary: | MT-Safe | AS-Unsafe lock | AC-Unsafe lock | See POSIX Safety Concepts.
该函数将进程的真实用户ID设置为ruid,将有效用户ID设置为euid。如果ruid为-1,表示不改变真实用户ID;同样euid为-1,表示不改变有效用户ID。
setreuid 函数的存在是为了与不支持文件 ID 的 4.3 BSD Unix 兼容。您可以使用此功能来交换进程的有效用户 ID 和真实用户 ID。(特权进程不限于此特定用途。)如果支持文件 ID,则应使用该功能而不是此功能。请参阅启用和禁用 Setuid 访问。
成功时返回值为 0,失败时返回值为 -1。为此函数定义了以下 errno 错误条件:
EPERM
该进程没有相应的权限;您无权更改为指定的 ID。
2.7. 设置组 ID
Setting the Group IDs
本节介绍用于更改进程的组 ID(真实和有效)的功能。要使用这些工具,您必须包含头文件 sys/types.h 和 unistd.h。
函数:int setegid (gid_t newgid)
Preliminary: | MT-Safe | AS-Unsafe lock | AC-Unsafe lock | See POSIX Safety Concepts.
此函数将进程的有效组 ID 设置为 newgid,前提是允许进程更改其组 ID。与 seteuid 一样,如果进程具有特权,它可以将其有效组 ID 更改为任何值;如果不是,但它有一个文件组 ID,那么它可能会更改为它的真实组 ID 或文件组 ID;否则它可能不会改变它的有效组 ID。
请注意,只有当有效用户 ID 为零时,进程才具有特权。有效组 ID 仅影响访问权限。
setegid 的返回值和错误条件与 seteuid 相同。
此函数仅在定义了 _POSIX_SAVED_IDS 时才存在。
函数:int setgid (gid_t newgid)
Preliminary: | MT-Safe | AS-Unsafe lock | AC-Unsafe lock | See POSIX Safety Concepts.
此函数将进程的真实组 ID 和有效组 ID 都设置为 newgid,前提是该进程具有特权。它还会删除文件组 ID(如果有)。
如果进程没有特权,则 setgid 的行为类似于 setegid。
setgid 的返回值和错误条件与 seteuid 相同。
函数:int setregid (gid_t rgid, gid_t egid)
Preliminary: | MT-Safe | AS-Unsafe lock | AC-Unsafe lock | See POSIX Safety Concepts.
该函数将进程的真实组 ID 设置为 rgid,将有效组 ID 设置为 egid。如果rgid为-1,表示不改变真实组ID;同样egid为-1,表示不改变有效组ID。
提供 setregid 函数是为了与不支持文件 ID 的 4.3 BSD Unix 兼容。您可以使用此功能交换进程的有效组 ID 和真实组 ID。(特权进程不限于此用法。)如果支持文件 ID,则应使用该功能而不是使用此功能。请参阅启用和禁用 Setuid 访问。
setregid 的返回值和错误条件与 setreuid 相同。
setuid 和 setgid 的行为不同,具体取决于当时的有效用户 ID 是否为零。如果它不为零,则它们的行为类似于 seteuid 和 setegid。如果是,他们会更改有效 ID 和真实 ID,并删除文件 ID。为避免混淆,我们建议您始终使用 seteuid 和 setegid,除非您知道有效用户 ID 为零并且您的意图是永久更改角色。这种情况很少见——大多数需要它的程序,例如 login 和 su,都已经编写好了。
请注意,如果您的程序对 root 以外的某个用户设置了 setuid,则无法永久删除权限。
该系统还允许特权进程更改其补充组 ID。要使用 setgroups 或 initgroups,您的程序应包含头文件 grp.h。
函数:int setgroups (size_t count, const gid_t *groups)
Preliminary: | MT-Safe | AS-Unsafe lock | AC-Unsafe lock | See POSIX Safety Concepts.
此函数设置进程的补充组 ID。它只能从特权进程调用。count 参数指定数组组中组 ID 的数量。
如果成功,此函数返回 0,错误时返回 -1。为此函数定义了以下 errno 错误条件:
EPERM
调用进程没有特权。
函数:int initgroups (const char *user, gid_t group)
Preliminary: | MT-Safe locale | AS-Unsafe dlopen plugin heap lock | AC-Unsafe corrupt mem fd lock | See POSIX Safety Concepts.
initgroups 函数将进程的补充组 ID 设置为用户名 user 的正常默认值。组组被自动包括在内。
此功能通过扫描用户所属的所有组的组数据库来工作。然后它用它构建的列表调用 setgroups。
返回值和错误条件与 setgroups 相同。
如果您对特定用户所属的组感兴趣,但不想更改进程的补充组 ID,则可以使用 getgrouplist。要使用 getgrouplist,您的程序应该包含头文件 grp.h。
函数:int getgrouplist (const char *user, gid_t group, gid_t *groups, int *ngroups)
Preliminary: | MT-Safe locale | AS-Unsafe dlopen plugin heap lock | AC-Unsafe corrupt mem fd lock | See POSIX Safety Concepts.
getgrouplist 函数扫描用户所属的所有组的组数据库。这些组对应的最多 *ngroups 个组 ID 存储在数组组中;该函数的返回值是实际存储的组 ID 数。如果 *ngroups 小于找到的组总数,则 getgrouplist 返回值 -1 并将实际组数存储在 *ngroups 中。组组自动包含在 getgrouplist 返回的组列表中。
以下是如何使用 getgrouplist 读取用户的所有补充组:
gid_t *
supplementary_groups (char *user)
{
int ngroups = 16;
gid_t *groups
= (gid_t *) xmalloc (ngroups * sizeof (gid_t));
struct passwd *pw = getpwnam (user);
if (pw == NULL)
return NULL;
if (getgrouplist (pw->pw_name, pw->pw_gid, groups, &ngroups) < 0)
{
groups = xreallocarray (ngroups, sizeof *groups);
getgrouplist (pw->pw_name, pw->pw_gid, groups, &ngroups);
}
return groups;
}
2.8. 启用和禁用 Setuid 访问
Enabling and Disabling Setuid Access
一个典型的 setuid 程序并不总是需要它的特殊访问权限。在不需要时关闭此访问是一个好主意,因此它不可能提供意外访问。
如果系统支持 _POSIX_SAVED_IDS 功能,您可以使用 seteuid 完成此操作。当游戏程序启动时,它的真实用户ID是jdoe,它的有效用户ID是games,它保存的用户ID也是games。程序应在开始时记录两个用户 ID 值,如下所示:
user_user_id = getuid ();
game_user_id = geteuid ();
然后它可以关闭游戏文件访问
seteuid (user_user_id);
并打开它
seteuid (game_user_id);
在整个过程中,真实用户 ID 仍然是 jdoe,而文件用户 ID 仍然是游戏,因此程序始终可以将其有效用户 ID 设置为任一。
在其他不支持文件用户 ID 的系统上,您可以通过使用 setreuid 交换进程的真实有效用户 ID 来打开和关闭 setuid 访问,如下所示:
setreuid(geteuid(),getuid());
这种特殊情况总是被允许的——它不会失败。
为什么这会产生切换 setuid 访问的效果?假设一个游戏程序刚刚启动,它的真实用户ID是jdoe,而它的有效用户ID是games。在这种状态下,游戏可以写入分数文件。如果交换两个uid,real变成games,effective变成jdoe;现在该程序只有 jdoe 访问权限。另一个交换将游戏带回有效用户 ID 并恢复对分数文件的访问。
为了处理这两种系统,使用预处理器条件测试保存的用户 ID 功能,如下所示:
#ifdef _POSIX_SAVED_IDS
seteuid (user_user_id);
#else
setreuid (geteuid (), getuid ());
#endif
2.9. Setuid 程序示例
Setuid Program Example
这是一个示例,展示了如何设置一个更改其有效用户 ID 的程序。
这是一个名为 caber-toss 的游戏程序的一部分,该程序操纵一个文件分数,该文件应该只能由游戏程序本身写入。该程序假定其可执行文件将使用 setuid 位设置并由与分数文件相同的用户拥有。通常,系统管理员会为此设置一个类似游戏的帐户。
可执行文件的模式为 4755,因此对其执行“ls -l”会产生如下输出:
-rwsr-xr-x 1 games 184422 Jul 30 15:17 caber-toss
setuid 位在文件模式中显示为“s”。
分数文件的模式为 644,在其上执行“ls -l”显示:
-rw-r--r-- 1 games 0 Jul 31 15:33 scores
以下是显示如何设置更改的用户 ID 的程序部分。该程序是有条件的,因此如果支持,它会使用文件 ID 功能,否则使用 setreuid 交换有效用户 ID 和真实用户 ID。
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
/* Remember the effective and real UIDs. */
static uid_t euid, ruid;
/* Restore the effective UID to its original value. */
void
do_setuid (void)
{
int status;
#ifdef _POSIX_SAVED_IDS
status = seteuid (euid);
#else
status = setreuid (ruid, euid);
#endif
if (status < 0) {
fprintf (stderr, "Couldn't set uid.\n");
exit (status);
}
}
/* Set the effective UID to the real UID. */
void
undo_setuid (void)
{
int status;
#ifdef _POSIX_SAVED_IDS
status = seteuid (ruid);
#else
status = setreuid (euid, ruid);
#endif
if (status < 0) {
fprintf (stderr, "Couldn't set uid.\n");
exit (status);
}
}
/* Main program. */
int
main (void)
{
/* Remember the real and effective user IDs. */
ruid = getuid ();
euid = geteuid ();
undo_setuid ();
/* Do the game and record the score. */
…
}
请注意 main 函数所做的第一件事是将有效用户 ID 设置回真实用户 ID。这样,在用户玩游戏时执行的任何其他文件访问都使用真实用户 ID 来确定权限。只有当程序需要打开分数文件时,它才会切换回文件用户 ID,如下所示:
/* Record the score. */
int
record_score (int score)
{
FILE *stream;
char *myname;
/* Open the scores file. */
do_setuid ();
stream = fopen (SCORES_FILE, "a");
undo_setuid ();
/* Write the score to the file. */
if (stream)
{
myname = cuserid (NULL);
if (score < 0)
fprintf (stream, "%10s: Couldn't lift the caber.\n", myname);
else
fprintf (stream, "%10s: %d feet.\n", myname, score);
fclose (stream);
return 0;
}
else
return -1;
}
2.10. 编写 Setuid 程序的技巧
Tips for Writing Setuid Programs
setuid 程序很容易给用户非预期的访问权限——事实上,如果你想避免这种情况,你需要小心。以下是一些指导方针,用于防止意外访问并在意外访问发生时将其后果降至最低:
-
除非绝对必要,否则不要使用具有特权用户 ID 的 setuid 程序,例如 root。如果资源特定于您的特定程序,最好定义一个新的、非特权用户 ID 或组 ID 来管理该资源。如果您可以编写程序以使用特殊组而不是特殊用户,那会更好。
-
将 exec 函数与更改有效用户 ID 结合使用时要谨慎。不要让您的程序的用户在更改的用户 ID 下执行任意程序。执行 shell 尤其是坏消息。不太明显的是,execlp 和 execvp 函数是一个潜在的风险(因为它们执行的程序取决于用户的 PATH 环境变量)。
如果您必须在更改的 ID 下执行另一个程序,请为可执行文件指定一个绝对文件名(请参阅文件名解析),并确保对该可执行文件和所有包含目录的保护使得普通用户无法将其替换为其他一些程序。
您还应该检查传递给程序的参数,以确保它们没有意外的影响。同样,您应该检查环境变量。决定哪些参数和变量是安全的,并拒绝所有其他的。
您永远不应该在特权程序中使用 system,因为它会调用 shell。
-
仅在实际使用该资源的程序部分中使用控制该资源的用户 ID。完成后,将有效用户 ID 恢复为实际用户的用户 ID。请参阅启用和禁用 Setuid 访问。
-
如果程序的 setuid 部分需要访问受控资源以外的其他文件,它应该验证真实用户通常是否有权访问这些文件。您可以使用访问功能(请参阅如何确定您对文件的访问)来检查这一点;它使用真实的用户和组 ID,而不是有效的 ID。
2.11. 识别登录者
Identifying Who Logged In
您可以使用本节中列出的函数来确定正在运行进程的用户的登录名,以及登录当前会话的用户的名称。另请参阅函数 getuid 和朋友(请参阅阅读进程的角色)。系统如何收集此信息以及如何从后台存储中控制/添加/删除信息在用户账号数据库中进行了描述。
getlogin 函数在 unistd.h 中声明,而 cuserid 和 L_cuserid 在 stdio.h 中声明。
函数:char * getlogin (void)
Preliminary: | MT-Unsafe race:getlogin race:utent sig:ALRM timer locale | AS-Unsafe dlopen plugin heap lock | AC-Unsafe corrupt lock fd mem | See POSIX Safety Concepts.
getlogin 函数返回一个指向字符串的指针,该字符串包含在进程的控制终端上登录的用户名,如果无法确定此信息,则返回一个空指针。该字符串是静态分配的,并且可能在随后调用此函数或 cuserid 时被覆盖。
函数:char * cuserid (char *string)
Preliminary: | MT-Unsafe race:cuserid/!string locale | AS-Unsafe dlopen plugin heap lock | AC-Unsafe corrupt lock fd mem | See POSIX Safety Concepts.
cuserid 函数返回一个指向字符串的指针,该字符串包含与进程的有效 ID 关联的用户名。如果 string 不是空指针,它应该是一个至少可以容纳 L_cuserid 字符的数组;字符串在此数组中返回。否则,返回指向静态区域中的字符串的指针。此字符串是静态分配的,可能会在随后调用此函数或 getlogin 时被覆盖。
不推荐使用此函数,因为它在 XPG4.2 中被标记为撤回,并且已从 POSIX.1 的较新版本中删除。
宏:int L_cuserid
一个整数常量,指示您可能需要在数组中存储用户名多长时间。
这些功能让您的程序可以肯定地识别正在运行的用户或登录此会话的用户。(当涉及 setuid 程序时,这些可能会有所不同;请参阅进程的角色。)用户不能做任何事情来欺骗这些函数。
对于大多数目的,使用环境变量 LOGNAME 来找出用户是谁会更有用。这更灵活正是因为用户可以任意设置LOGNAME。请参阅标准环境变量。
2.12. 用户账号数据库
The User Accounting Database
大多数类 Unix 操作系统通过维护用户账号数据库来跟踪登录用户。这个用户账号数据库存储了每个终端,谁登录,什么时间,用户登录shell的进程ID等,还存储了系统的运行级别,上次的时间等信息。系统重启,可能更多。
用户账号数据库通常位于 /etc/utmp、/var/adm/utmp 或 /var/run/utmp。但是,绝不应直接访问这些文件。对于从用户账号数据库读取信息和向其写入信息,应使用本节中描述的功能。
2.12.1. 操作用户账号数据库
Manipulating the User Accounting Database
这些函数和相应的数据结构在头文件 utmp.h 中声明。
数据类型:struct exit_status
exit_status 数据结构用于保存有关用户帐户数据库中标记为 DEAD_PROCESS 的进程的退出状态的信息。
short int e_termination
进程的退出状态。
short int e_exit
进程的退出状态。
数据类型:struct utmp
utmp 数据结构用于保存有关用户账号数据库中条目的信息。在 GNU 系统上,它具有以下成员:
short int ut_type
指定登录类型,EMPTY、RUN_LVL、BOOT_TIME、OLD_TIME、NEW_TIME、INIT_PROCESS、LOGIN_PROCESS、USER_PROCESS、DEAD_PROCESS 或 ACCOUNTING 之一。
pid_t ut_pid
登录进程的进程ID号。
char ut_line[]
tty 的设备名称(不带 /dev/)。
char ut_id[]
进程的 inittab ID。
char ut_user[]
用户的登录名。
char ut_host[]
用户登录的主机的名称。
struct exit_status ut_exit
标记为 DEAD_PROCESS 的进程的退出状态。
long ut_session
会话 ID,用于窗口化。
struct timeval ut_tv
输入的时间。对于 OLD_TIME 类型的条目,这是系统时钟更改的时间,对于 NEW_TIME 类型的条目,这是系统时钟设置的时间。
int32_t ut_addr_v6[4]
远程主机的 Internet 地址。
ut_type、ut_pid、ut_id、ut_tv 和 ut_host 字段并非在所有系统上都可用。因此,应为这些情况准备便携式应用程序。为了帮助做到这一点,utmp.h 标头提供了宏 _HAVE_UT_TYPE、_HAVE_UT_PID、_HAVE_UT_ID、_HAVE_UT_TV 和 _HAVE_UT_HOST(如果相应的字段可用)。程序员可以通过在程序代码中使用#ifdef 来处理这种情况。
以下宏被定义为用作 utmp 结构的 ut_type 成员的值。这些值是整数常量。
EMPTY
此宏用于指示该条目不包含有效的用户记帐信息。
RUN_LVL
该宏用于识别系统的运行级别。
BOOT_TIME
该宏用于识别系统启动的时间。
OLD_TIME
该宏用于识别系统时钟发生变化的时间。
NEW_TIME
该宏用于识别系统时钟改变后的时间。
INIT_PROCESS
此宏用于标识由 init 进程产生的进程。
LOGIN_PROCESS
该宏用于识别登录用户的会话负责人。
USER_PROCESS
该宏用于标识用户进程。
DEAD_PROCESS
此宏用于标识终止的进程。
ACCOUNTING
???
ut_line、ut_id、ut_user 和 ut_host 数组的大小可以使用 sizeof 运算符找到。
许多旧系统有一个 ut_time 成员,而不是 ut_tv 成员,通常是 time_t 类型,用于表示与条目关联的时间。因此,仅为了向后兼容,utmp.h 将 ut_time 定义为 ut_tv.tv_sec 的别名。
函数:void setutent (void)
Preliminary: | MT-Unsafe race:utent | AS-Unsafe lock | AC-Unsafe lock fd | See POSIX Safety Concepts.
此函数打开用户账号数据库以开始扫描它。然后,您可以调用 getutent、getutid 或 getutline 来读取条目并调用 pututline 来写入条目。
如果数据库已经打开,它会将输入重置为数据库的开头。
函数:struct utmp * getutent (void)
Preliminary: | MT-Unsafe init race:utent race:utentbuf sig:ALRM timer | AS-Unsafe heap lock | AC-Unsafe lock fd mem | See POSIX Safety Concepts.
getutent 函数从用户账号数据库中读取下一个条目。它返回一个指向该条目的指针,该条目是静态分配的,并且可能被后续对 getutent 的调用覆盖。如果您希望保存信息,则必须复制结构的内容,或者您可以使用 getutent_r 函数将数据存储在用户提供的缓冲区中。
如果没有更多条目可用,则返回空指针。
函数:void endutent (void)
Preliminary: | MT-Unsafe race:utent | AS-Unsafe lock | AC-Unsafe lock fd | See POSIX Safety Concepts.
此函数关闭用户账号数据库。
函数:struct utmp * getutid (const struct utmp *id)
Preliminary: | MT-Unsafe init race:utent sig:ALRM timer | AS-Unsafe lock heap | AC-Unsafe lock mem fd | See POSIX Safety Concepts.
此函数从数据库中的当前点向前搜索与 id 匹配的条目。如果 id 结构的 ut_type 成员是 RUN_LVL、BOOT_TIME、OLD_TIME 或 NEW_TIME 之一,则如果 ut_type 成员相同,则条目匹配。如果 id 结构的 ut_type 成员是 INIT_PROCESS、LOGIN_PROCESS、USER_PROCESS 或 DEAD_PROCESS,则如果从数据库读取的条目的 ut_type 成员是这四个之一,并且 ut_id 成员匹配,则条目匹配。但是,如果 id 结构或从数据库读取的条目的 ut_id 成员为空,它会检查 ut_line 成员是否匹配。如果找到匹配的条目,getutid 将返回指向该条目的指针,该指针是静态分配的,并且可能会被后续调用 getutent、getutid 或 getutline 覆盖。如果您想保存信息,您必须复制结构的内容。
如果在没有匹配的情况下到达数据库的末尾,则返回一个空指针。
getutid 函数可以缓存最后读取的条目。因此,如果您使用 getutid 搜索多个匹配项,则需要在每次调用后将静态数据清零。否则 getutid 可能只是一遍又一遍地返回指向同一条目的指针。
函数:struct utmp * getutline (const struct utmp *line)
Preliminary: | MT-Unsafe init race:utent sig:ALRM timer | AS-Unsafe heap lock | AC-Unsafe lock fd mem | See POSIX Safety Concepts.
该函数从数据库中的当前点向前搜索,直到找到一个 ut_type 值为 LOGIN_PROCESS 或 USER_PROCESS 且其 ut_line 成员与行结构的 ut_line 成员匹配的条目。如果找到这样的条目,它会返回一个指向静态分配的条目的指针,并且可能会被后续调用 getutent、getutid 或 getutline 覆盖。如果您想保存信息,您必须复制结构的内容。
如果在没有匹配的情况下到达数据库的末尾,则返回一个空指针。
getutline 函数可以缓存最后读取的条目。因此,如果您使用 getutline 搜索多个匹配项,则有必要在每次调用后将静态数据清零。否则 getutline 可能只是一遍又一遍地返回指向同一条目的指针。
函数:struct utmp * pututline (const struct utmp *utmp)
Preliminary: | MT-Unsafe race:utent sig:ALRM timer | AS-Unsafe lock | AC-Unsafe lock fd | See POSIX Safety Concepts.
pututline 函数将条目 *utmp 插入到用户账号数据库中的适当位置。如果它发现它不在数据库中的正确位置,它使用getutid搜索插入条目的位置,但这不会修改getutent、getutid和getutline返回的静态结构。如果此搜索失败,则将该条目附加到数据库中。
pututline 函数返回一个指针,该指针指向插入到用户账号数据库中的条目的副本,如果无法添加条目,则返回一个空指针。为此函数定义了以下 errno 错误条件:
EPERM
该进程没有相应的权限;您不能修改用户账号数据库。
前面提到的所有 get* 函数都将它们返回的信息存储在静态缓冲区中。这在多线程程序中可能是一个问题,因为为请求返回的数据被另一个线程中的返回值数据覆盖。因此,GNU C 库作为扩展提供了另外三个函数,它们在用户提供的缓冲区中返回数据。
函数:int getutent_r (struct utmp *buffer, struct utmp **result)
Preliminary: | MT-Unsafe race:utent sig:ALRM timer | AS-Unsafe lock | AC-Unsafe lock fd | See POSIX Safety Concepts.
getutent_r 等效于 getutent 函数。它从数据库中返回下一个条目。但不是将信息存储在静态缓冲区中,而是将其存储在参数缓冲区指向的缓冲区中。
如果调用成功,则函数返回 0,并且参数 result 指向的指针变量包含指向包含结果的缓冲区的指针(这很可能与缓冲区的值相同)。如果在执行 getutent_r 期间出现问题,该函数将返回 -1。
这个函数是一个 GNU 扩展。
函数:int getutid_r (const struct utmp *id, struct utmp *buffer, struct utmp **result)
Preliminary: | MT-Unsafe race:utent sig:ALRM timer | AS-Unsafe lock | AC-Unsafe lock fd | See POSIX Safety Concepts.
这个函数像 getutid 一样检索与 id 中存储的信息匹配的下一个条目。但结果存储在参数缓冲区指向的缓冲区中。
如果成功,则函数返回 0,并且参数 result 指向的指针变量包含指向缓冲区的指针(可能与结果相同。如果不成功,则函数返回 -1。
这个函数是一个 GNU 扩展。
函数:int getutline_r (const struct utmp *line, struct utmp *buffer, struct utmp **result)
Preliminary: | MT-Unsafe race:utent sig:ALRM timer | AS-Unsafe lock | AC-Unsafe lock fd | See POSIX Safety Concepts.
这个函数像 getutline 一样检索与存储在 line 中的信息匹配的下一个条目。但结果存储在参数缓冲区指向的缓冲区中。
如果成功,则函数返回 0,并且参数 result 指向的指针变量包含指向缓冲区的指针(可能与结果相同。如果不成功,则函数返回 -1。
这个函数是一个 GNU 扩展。
除了用户账号数据库之外,大多数系统还保留了许多类似的数据库。例如,大多数系统保留一个包含所有先前登录的日志文件(通常在 /etc/wtmp 或 /var/log/wtmp 中)。
为了指定要检查的数据库,应使用以下函数。
函数:int utmpname (const char *file)
Preliminary: | MT-Unsafe race:utent | AS-Unsafe lock heap | AC-Unsafe lock mem | See POSIX Safety Concepts.
utmpname 函数将要检查的数据库的名称更改为文件,并关闭任何以前打开的数据库。默认情况下,getutent、getutid、getutline 和 pututline 读取和写入用户账号数据库。
定义了以下宏用作文件参数:
宏:char * _PATH_UTMP
该宏用于指定用户账号数据库。
宏:char * _PATH_WTMP
该宏用于指定用户记帐日志文件。
如果成功存储了新名称,则 utmpname 函数返回值 0,返回值 -1 表示错误。请注意,utmpname 不会尝试打开数据库,因此返回值不会说明数据库是否可以成功打开。
GNU C 库专门为维护类日志数据库提供以下函数:
函数:void updwtmp (const char *wtmp_file, const struct utmp *utmp)
Preliminary: | MT-Unsafe sig:ALRM timer | AS-Unsafe | AC-Unsafe fd | See POSIX Safety Concepts.
updwtmp 函数将条目 *utmp 附加到由 wtmp_file 指定的数据库中。有关 wtmp_file 参数的可能值,请参见 utmpname 函数。
可移植性 注意:尽管许多操作系统提供了这些功能的一个子集,但它们并未标准化。返回类型往往存在细微差别,struct utmp 的各种定义也存在相当大的差异。在为 GNU C 库编程时,最好坚持使用本节中描述的函数。但是,如果您希望您的程序具有可移植性,请考虑使用 XPG 用户账号数据库函数中描述的 XPG 功能,或者查看登录和注销中的 BSD 兼容功能。
2.12.2. XPG 用户账号数据库函数
XPG User Accounting Database Functions
X/Open Portability Guide 中描述的这些函数在头文件 utmpx.h 中声明。
数据类型:struct utmpx
utmpx 数据结构至少包含以下成员:
short int ut_type
指定登录类型,EMPTY、RUN_LVL、BOOT_TIME、OLD_TIME、NEW_TIME、INIT_PROCESS、LOGIN_PROCESS、USER_PROCESS 或 DEAD_PROCESS 之一。
pid_t ut_pid
登录进程的进程ID号。
char ut_line[]
tty 的设备名称(不带 /dev/)。
char ut_id[]
进程的 inittab ID。
char ut_user[]
用户的登录名。
struct timeval ut_tv
输入的时间。对于 OLD_TIME 类型的条目,这是系统时钟更改的时间,对于 NEW_TIME 类型的条目,这是系统时钟设置的时间。
在 GNU C 库中,struct utmpx 与 struct utmp 相同,只是包含 utmpx.h 不会使 struct exit_status 的声明可见。
以下宏定义用作 utmpx 结构的 ut_type 成员的值。这些值是整数常量,并且在 GNU C 库中与 utmp.h 中的定义相同。
EMPTY
此宏用于指示该条目不包含有效的用户记帐信息。
RUN_LVL
该宏用于识别系统的运行级别。
BOOT_TIME
该宏用于识别系统启动的时间。
OLD_TIME
该宏用于识别系统时钟发生变化的时间。
NEW_TIME
该宏用于识别系统时钟改变后的时间。
INIT_PROCESS
此宏用于标识由 init 进程产生的进程。
LOGIN_PROCESS
该宏用于识别登录用户的会话负责人。
USER_PROCESS
该宏用于标识用户进程。
DEAD_PROCESS
此宏用于标识终止的进程。
ut_line、ut_id 和 ut_user 数组的大小可以使用 sizeof 运算符找到。
函数:void setutxent(void)
Preliminary: | MT-Unsafe race:utent | AS-Unsafe lock | AC-Unsafe lock fd | See POSIX Safety Concepts.
此功能类似于 setutent。在 GNU C 库中,它只是 setutent 的别名。
函数:struct utmpx * getutxent (void)
Preliminary: | MT-Unsafe init race:utent sig:ALRM timer | AS-Unsafe heap lock | AC-Unsafe lock fd mem | See POSIX Safety Concepts.
getutxent 函数类似于 getutent,但返回一个指向 struct utmpx 而不是 struct utmp 的指针。在 GNU C 库中,它只是 getutent 的别名。
函数:void endutxent (void)
Preliminary: | MT-Unsafe race:utent | AS-Unsafe lock | AC-Unsafe lock | See POSIX Safety Concepts.
此功能类似于 endutent。在 GNU C 库中,它只是 endutent 的别名。
函数:struct utmpx * getutxid (const struct utmpx *id)
Preliminary: | MT-Unsafe init race:utent sig:ALRM timer | AS-Unsafe lock heap | AC-Unsafe lock mem fd | See POSIX Safety Concepts.
此函数类似于 getutid,但使用 struct utmpx 而不是 struct utmp。在 GNU C 库中,它只是 getutid 的别名。
函数:struct utmpx * getutxline (const struct utmpx *line)
Preliminary: | MT-Unsafe init race:utent sig:ALRM timer | AS-Unsafe heap lock | AC-Unsafe lock fd mem | See POSIX Safety Concepts.
此函数类似于 getutid,但使用 struct utmpx 而不是 struct utmp。在 GNU C 库中,它只是 getutline 的别名。
函数:struct utmpx * pututxline (const struct utmpx *utmp)
Preliminary: | MT-Unsafe race:utent sig:ALRM timer | AS-Unsafe lock | AC-Unsafe lock fd | See POSIX Safety Concepts.
pututxline 函数在功能上与 pututline 相同,但使用 struct utmpx 而不是 struct utmp。在 GNU C 库中,pututxline 只是 pututline 的别名。
函数:int utmpxname (const char *file)
Preliminary: | MT-Unsafe race:utent | AS-Unsafe lock heap | AC-Unsafe lock mem | See POSIX Safety Concepts.
utmpxname 函数在功能上与 utmpname 相同。在 GNU C 库中,utmpxname 只是 utmpname 的别名。
您可以使用以下函数在传统结构 utmp 和 XPG 结构 utmpx 之间进行转换。在 GNU C 库中,这些函数只是副本,因为这两个结构是相同的。
函数:int getutmp (const struct utmpx *utmpx, struct utmp *utmp)
Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.
getutmp 在结构兼容的情况下将信息从 utmpx 复制到 utmp。
函数:int getutmpx (const struct utmp *utmp, struct utmpx *utmpx)
Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.
getutmpx 在结构兼容的情况下将信息从 utmp 复制到 utmpx。
2.12.3. 登录和注销
Logging In and Out
这些从 BSD 派生的函数在单独的 libutil 库中可用,并在 utmp.h 中声明。
请注意,struct utmp 的 ut_user 成员在 BSD 中称为 ut_name。因此,ut_name 在 utmp.h 中被定义为 ut_user 的别名。
函数:int login_tty (int filedes)
Preliminary: | MT-Unsafe race:ttyname | AS-Unsafe heap lock | AC-Unsafe lock fd mem | See POSIX Safety Concepts.
该函数使filedes成为当前进程的控制终端,将标准输入、标准输出和标准错误输出重定向到该终端,并关闭filedes。
此函数在成功完成时返回 0,在错误时返回 -1。
函数:void login (const struct utmp *entry)
Preliminary: | MT-Unsafe race:utent sig:ALRM timer | AS-Unsafe lock heap | AC-Unsafe lock corrupt fd mem | See POSIX Safety Concepts.
登录功能将一个条目插入到用户记帐数据库中。ut_line 成员设置为标准输入上的终端名称。如果标准输入不是终端登录,则使用标准输出或标准错误输出来确定终端的名称。如果 struct utmp 有 ut_type 成员,login 将其设置为 USER_PROCESS,如果有 ut_pid 成员,则将其设置为当前进程的进程 ID。其余条目从条目中复制。
该条目的副本将写入用户记帐日志文件。
函数:int logout (const char *ut_line)
Preliminary: | MT-Unsafe race:utent sig:ALRM timer | AS-Unsafe lock heap | AC-Unsafe lock fd mem | See POSIX Safety Concepts.
此函数修改用户记帐数据库以指示 ut_line 上的用户已注销。
如果条目成功写入数据库,则 logout 函数返回 1,如果出错则返回 0。
函数:void logwtmp (const char *ut_line, const char *ut_name, const char *ut_host)
Preliminary: | MT-Unsafe sig:ALRM timer | AS-Unsafe | AC-Unsafe fd | See POSIX Safety Concepts.
logwtmp 函数将一个条目附加到用户记帐日志文件中,用于当前时间和 ut_line、ut_name 和 ut_host 参数中提供的信息。
可移植性注意:BSD struct utmp 只有 ut_line、ut_name、ut_host 和 ut_time 成员。旧系统甚至没有 ut_host 成员。
2.13. 用户数据库
User Database
本节介绍如何搜索和扫描注册用户的数据库。在大多数系统上,数据库本身保存在文件 /etc/passwd 中,但在某些系统上,特殊的网络服务器可以访问它。
从历史上看,该数据库包括用户密码短语的单向哈希(请参阅密码短语存储)以及每个用户的公共信息(例如他们的用户 ID 和全名)。与该数据库相关的许多函数和数据结构,以及文件名 /etc/passwd 本身,都反映了这一历史。然而,这个数据库中的信息对所有用户都是可用的,并且让所有用户都可以使用密码短语哈希不再被认为是安全的,因此它们已被移至只能以特殊权限访问的“影子”数据库。
2.13.1. 描述用户的数据结构
The Data Structure that Describes a User
用于访问系统用户数据库的函数和数据结构在头文件 pwd.h 中声明。
数据类型:struct passwd
passwd 数据结构用于保存有关系统用户数据库中条目的信息。它至少有以下成员:
char *pw_name
用户的登录名。
char *pw_passwd
从历史上看,该字段将保存用户密码的单向哈希。如今,它几乎总是单个字符“x”,表示哈希在影子数据库中。
uid_t pw_uid
用户 ID 号。
gid_t pw_gid
用户的默认组 ID 号。
char *pw_gecos
一个字符串,通常包含用户的真实姓名,可能还有其他信息,例如电话号码。
char *pw_dir
用户的主目录或初始工作目录。这可能是一个空指针,在这种情况下,解释是系统相关的。
char *pw_shell
用户的默认shell,或者用户登录时运行的初始程序。这可能是一个空指针,表示应该使用系统默认值。
2.13.2. 查找一个用户
Looking Up One User
您可以使用 getpwuid 或 getpwnam 在系统用户数据库中搜索有关特定用户的信息。这些函数在 pwd.h 中声明。
函数:struct passwd * getpwuid (uid_t uid)
Preliminary: | MT-Unsafe race:pwuid locale | AS-Unsafe dlopen plugin heap lock | AC-Unsafe corrupt lock fd mem | See POSIX Safety Concepts.
此函数返回一个指向静态分配结构的指针,该结构包含有关用户 ID 为 uid 的用户的信息。此结构可能会在后续调用 getpwuid 时被覆盖。
空指针值表示数据库中没有用户 ID 为 uid 的用户。
函数:int getpwuid_r (uid_t uid, struct passwd *result_buf, char *buffer, size_t buflen, struct passwd **result)
Preliminary: | MT-Safe locale | AS-Unsafe dlopen plugin heap lock | AC-Unsafe corrupt lock fd mem | See POSIX Safety Concepts.
此函数与 getpwuid 类似,它返回有关用户 ID 为 uid 的用户的信息。但是,它使用信息填充由 result_buf 指向的用户提供的结构,而不是使用静态缓冲区。buffer 指向的附加缓冲区的第一个 buflen 字节用于包含附加信息,通常是结果结构的元素所指向的字符串。
如果找到 ID 为 uid 的用户,则 result 中返回的指针指向包含所需数据的记录(即 result 包含值 result_buf)。如果未找到用户或发生错误,则结果中返回的指针为空指针。该函数返回零或错误代码。如果缓冲区缓冲区太小而无法包含所有需要的信息,则返回错误代码 ERANGE 并将 errno 设置为 ERANGE。
函数:struct passwd * getpwnam (const char *name)
Preliminary: | MT-Unsafe race:pwnam locale | AS-Unsafe dlopen plugin heap lock | AC-Unsafe corrupt lock fd mem | See POSIX Safety Concepts.
此函数返回一个指向静态分配结构的指针,该结构包含有关用户名为 name 的用户的信息。此结构可能会在后续调用 getpwnam 时被覆盖。
空指针返回表示没有用户名为 name。
函数:int getpwnam_r (const char *name, struct passwd *result_buf, char *buffer, size_t buflen, struct passwd **result)
Preliminary: | MT-Safe locale | AS-Unsafe dlopen plugin heap lock | AC-Unsafe corrupt lock fd mem | See POSIX Safety Concepts.
此函数与 getpwnam 类似,它返回有关用户名为 name 的用户的信息。但是,与 getpwuid_r 一样,它使用信息填充用户在 result_buf 和 buffer 中提供的缓冲区,而不是使用静态缓冲区。
返回值与 getpwuid_r 相同。
2.13.3. 扫描所有用户列表
Scanning the List of All Users
本节说明程序如何读取系统中所有用户的列表,一次一个用户。此处描述的函数在 pwd.h 中声明。
您可以使用 fgetpwent 函数从特定文件中读取用户条目。
函数:struct passwd * fgetpwent (FILE *stream)
Preliminary: | MT-Unsafe race:fpwent | AS-Unsafe corrupt lock | AC-Unsafe corrupt lock | See POSIX Safety Concepts.
此函数从流中读取下一个用户条目并返回指向该条目的指针。该结构是静态分配的,并在后续调用 fgetpwent 时重写。如果您想保存信息,您必须复制结构的内容。
流必须对应于与标准用户数据库文件格式相同的文件。
函数:int fgetpwent_r (FILE *stream, struct passwd *result_buf, char *buffer, size_t buflen, struct passwd **result)
Preliminary: | MT-Safe | AS-Unsafe corrupt | AC-Unsafe corrupt lock | See POSIX Safety Concepts.
这个函数与 fgetpwent 类似,它从流中读取下一个用户条目。但是结果是在result_buf指向的结构中返回的。buffer 指向的附加缓冲区的第一个 buflen 字节用于包含附加信息,通常是结果结构的元素所指向的字符串。
流必须对应于与标准用户数据库文件格式相同的文件。
如果函数返回零结果指向具有所需数据的结构(通常在 result_buf 中)。如果发生错误,则返回值非零且结果包含空指针。
扫描用户数据库中所有条目的方法是使用 setpwent、getpwent 和 endpwent。
函数:void setpwent(void)
Preliminary: | MT-Unsafe race:pwent locale | AS-Unsafe dlopen plugin heap lock | AC-Unsafe corrupt lock fd mem | See POSIX Safety Concepts.
此函数初始化 getpwent 和 getpwent_r 用于读取用户数据库的流。
函数:struct passwd * getpwent (void)
Preliminary: | MT-Unsafe race:pwent race:pwentbuf locale | AS-Unsafe dlopen plugin heap lock | AC-Unsafe corrupt lock fd mem | See POSIX Safety Concepts.
getpwent 函数从由 setpwent 初始化的流中读取下一个条目。它返回一个指向条目的指针。该结构是静态分配的,并在后续调用 getpwent 时重写。如果您想保存信息,您必须复制结构的内容。
当没有更多条目可用时,将返回一个空指针。
函数:int getpwent_r (struct passwd *result_buf, char *buffer, size_t buflen, struct passwd **result)
Preliminary: | MT-Unsafe race:pwent locale | AS-Unsafe dlopen plugin heap lock | AC-Unsafe corrupt lock fd mem | See POSIX Safety Concepts.
此函数与 getpwent 类似,它从由 setpwent 初始化的流中返回下一个条目。与 fgetpwent_r 一样,它使用用户在 result_buf 和 buffer 中提供的缓冲区来返回请求的信息。
返回值与 fgetpwent_r 相同。
函数:void endpwent (void)
Preliminary: | MT-Unsafe race:pwent locale | AS-Unsafe dlopen plugin heap lock | AC-Unsafe corrupt lock fd mem | See POSIX Safety Concepts.
此函数关闭 getpwent 或 getpwent_r 使用的内部流。
2.13.4. 编写用户条目
Writing a User Entry
函数:int putpwent (const struct passwd *p, FILE *stream)
Preliminary: | MT-Safe locale | AS-Unsafe corrupt | AC-Unsafe lock corrupt | See POSIX Safety Concepts.
此函数以标准用户数据库文件所用的格式将用户条目 *p 写入流流。成功时返回值为零,失败时返回非零。
此函数的存在是为了与 SVID 兼容。我们建议您避免使用它,因为它仅在 struct passwd 结构除了标准成员之外没有其他成员的假设下才有意义,在将传统 Unix 数据库与其他关于用户的扩展信息合并的系统上,使用此功能添加条目将不可避免地遗漏许多重要信息。
如果组或用户名以 - 或 + 开头,则组和用户 ID 字段留空。
函数 putpwent 在 pwd.h 中声明。
2.14. 组数据库
Group Database
本节介绍如何搜索和扫描已注册群组的数据库。在大多数系统上,数据库本身保存在文件 /etc/group 中,但在某些系统上,特殊的网络服务提供对它的访问。
2.14.1. 组的数据结构
The Data Structure for a Group
用于访问系统组数据库的函数和数据结构在头文件 grp.h 中声明。
数据类型:struct group
组结构用于保存有关系统组数据库中条目的信息。它至少有以下成员:
char *gr_name
组的名称。
gid_t gr_gid
组的组 ID。
char **gr_mem
指向组中用户名称的指针向量。每个用户名都是一个以空字符结尾的字符串,向量本身以空指针结尾。
2.14.2. 查找一组
Looking Up One Group
您可以使用 getgrgid 或 getgrnam 在组数据库中搜索有关特定组的信息。这些函数在 grp.h 中声明。
函数:struct group * getgrgid (gid_t gid)
Preliminary: | MT-Unsafe race:grgid locale | AS-Unsafe dlopen plugin heap lock | AC-Unsafe corrupt lock fd mem | See POSIX Safety Concepts.
此函数返回一个指向静态分配结构的指针,该结构包含有关组 ID 为 gid 的组的信息。此结构可能会被后续调用 getgrgid 覆盖。
空指针表示没有 ID 为 gid 的组。
函数:int getgrgid_r (gid_t gid, struct group *result_buf, char *buffer, size_t buflen, struct group **result)
Preliminary: | MT-Safe locale | AS-Unsafe dlopen plugin heap lock | AC-Unsafe corrupt lock fd mem | See POSIX Safety Concepts.
此函数与 getgrgid 类似,它返回有关组 ID 为 gid 的组的信息。但是,它使用信息填充由 result_buf 指向的用户提供的结构,而不是使用静态缓冲区。buffer 指向的附加缓冲区的第一个 buflen 字节用于包含附加信息,通常是结果结构的元素所指向的字符串。
如果找到 ID 为 gid 的组,则 result 返回的指针指向包含所需数据的记录(即 result 包含值 result_buf)。如果未找到组或发生错误,则结果中返回的指针为空指针。该函数返回零或错误代码。如果缓冲区缓冲区太小而无法包含所有需要的信息,则返回错误代码 ERANGE 并将 errno 设置为 ERANGE。
函数:struct group * getgrnam (const char *name)
Preliminary: | MT-Unsafe race:grnam locale | AS-Unsafe dlopen plugin heap lock | AC-Unsafe corrupt lock fd mem | See POSIX Safety Concepts.
此函数返回一个指向静态分配结构的指针,该结构包含有关组名为 name 的组的信息。此结构可能会被后续调用 getgrnam 覆盖。
空指针表示没有名为 name 的组。
函数:int getgrnam_r (const char *name, struct group *result_buf, char *buffer, size_t buflen, struct group **result)
Preliminary: | MT-Safe locale | AS-Unsafe dlopen plugin heap lock | AC-Unsafe corrupt lock fd mem | See POSIX Safety Concepts.
此函数与 getgrnam 相似,因为它返回有关组名为 name 的组的信息。与 getgrgid_r 一样,它使用用户在 result_buf 和缓冲区中提供的缓冲区,而不是静态缓冲区。
返回值与 getgrgid_r 相同。
2.14.3. 扫描所有组列表
Scanning the List of All Groups
本节说明程序如何读取系统中所有组的列表,一次一组。此处描述的函数在 grp.h 中声明。
您可以使用 fgetgrent 函数从特定文件中读取组条目。
函数:struct group * fgetgrent (FILE *stream)
Preliminary: | MT-Unsafe race:fgrent | AS-Unsafe corrupt lock | AC-Unsafe corrupt lock | See POSIX Safety Concepts.
fgetgrent 函数从流中读取下一个条目。它返回一个指向条目的指针。该结构是静态分配的,并在后续调用 fgetgrent 时被覆盖。如果您想保存信息,您必须复制结构的内容。
该流必须对应于与标准组数据库文件格式相同的文件。
函数:int fgetgrent_r (FILE *stream, struct group *result_buf, char *buffer, size_t buflen, struct group **result)
Preliminary: | MT-Safe | AS-Unsafe corrupt | AC-Unsafe corrupt lock | See POSIX Safety Concepts.
这个函数与 fgetgrent 类似,它从流中读取下一个用户条目。但是结果是在result_buf指向的结构中返回的。buffer 指向的附加缓冲区的第一个 buflen 字节用于包含附加信息,通常是结果结构的元素所指向的字符串。
此流必须对应于与标准组数据库文件格式相同的文件。
如果函数返回零结果指向具有所需数据的结构(通常在 result_buf 中)。如果发生错误,则返回值非零且结果包含空指针。
扫描组数据库中所有条目的方法是使用 setgrent、getgrent 和 endgrent。
函数:void setgrent(void)
Preliminary: | MT-Unsafe race:grent locale | AS-Unsafe dlopen plugin heap lock | AC-Unsafe corrupt lock fd mem | See POSIX Safety Concepts.
此函数初始化用于从组数据库中读取的流。您可以通过调用 getgrent 或 getgrent_r 来使用此流。
函数:struct group * getgrent (void)
Preliminary: | MT-Unsafe race:grent race:grentbuf locale | AS-Unsafe dlopen plugin heap lock | AC-Unsafe corrupt lock fd mem | See POSIX Safety Concepts.
getgrent 函数从由 setgrent 初始化的流中读取下一个条目。它返回一个指向条目的指针。该结构是静态分配的,并在后续调用 getgrent 时被覆盖。如果您想保存信息,您必须复制结构的内容。
函数:int getgrent_r (struct group *result_buf, char *buffer, size_t buflen, struct group **result)
Preliminary: | MT-Unsafe race:grent locale | AS-Unsafe dlopen plugin heap lock | AC-Unsafe corrupt lock fd mem | See POSIX Safety Concepts.
此函数与 getgrent 相似,因为它从由 setgrent 初始化的流中返回下一个条目。与 fgetgrent_r 一样,它将结果放在由 result_buf 和 buffer 指向的用户提供的缓冲区中。
如果函数返回零,则结果包含指向数据的指针(通常等于 result_buf)。如果发生错误,则返回值非零且结果包含空指针。
函数:void endgrent (void)
Preliminary: | MT-Unsafe race:grent locale | AS-Unsafe dlopen plugin heap lock | AC-Unsafe corrupt lock fd mem | See POSIX Safety Concepts.
此函数关闭 getgrent 或 getgrent_r 使用的内部流。
2.15. 用户和组数据库示例
User and Group Database Example
这是一个示例程序,展示了系统数据库查询功能的使用。该程序打印有关运行该程序的用户的一些信息。
#include <grp.h>
#include <pwd.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
int
main (void)
{
uid_t me;
struct passwd *my_passwd;
struct group *my_group;
char **members;
/* Get information about the user ID. */
me = getuid ();
my_passwd = getpwuid (me);
if (!my_passwd)
{
printf ("Couldn't find out about user %d.\n", (int) me);
exit (EXIT_FAILURE);
}
/* Print the information. */
printf ("I am %s.\n", my_passwd->pw_gecos);
printf ("My login name is %s.\n", my_passwd->pw_name);
printf ("My uid is %d.\n", (int) (my_passwd->pw_uid));
printf ("My home directory is %s.\n", my_passwd->pw_dir);
printf ("My default shell is %s.\n", my_passwd->pw_shell);
/* Get information about the default group ID. */
my_group = getgrgid (my_passwd->pw_gid);
if (!my_group)
{
printf ("Couldn't find out about group %d.\n",
(int) my_passwd->pw_gid);
exit (EXIT_FAILURE);
}
/* Print the information. */
printf ("My default group is %s (%d).\n",
my_group->gr_name, (int) (my_passwd->pw_gid));
printf ("The members of this group are:\n");
members = my_group->gr_mem;
while (*members)
{
printf (" %s\n", *(members));
members++;
}
return EXIT_SUCCESS;
}
这是该程序的一些输出:
I am Throckmorton Snurd.
My login name is snurd.
My uid is 31093.
My home directory is /home/fsg/snurd.
My default shell is /bin/sh.
My default group is guest (12).
The members of this group are:
friedman
tami
2.16. 网络组数据库
Netgroup Database
2.16.1. 网络组数据
Netgroup Data
有时根据其他标准对用户进行分组很有用(请参阅组数据库)。例如,将某组用户与某台机器相关联是很有用的。另一方面,目前还不支持对主机名进行分组。
在 Sun Microsystems 的 SunOS 中出现了一种新型数据库,即 netgroup 数据库。它允许自由地对主机、用户和域进行分组,并赋予它们单独的名称。更具体地说,网络组是由主机名、用户名和域名组成的三元组列表,其中任何条目都可以是匹配所有输入的通配符条目。最后一种可能性是其他网络组的名称也可以在指定网络组的列表中给出。因此,可以构建任意层次结构而无需循环。
Sun 的实现只允许 nis 或 nisplus 服务的网络组,请参阅 NSS 配置文件中的服务。GNU C 库中的实现没有这样的限制。任一输入服务中的条目必须具有以下形式:
groupname ( groupname | (hostname,username,domainname) )+
三元组中的任何字段都可以为空,这意味着任何匹配。在描述函数时,我们将看到相反的情况也很有用。即,可能存在与任何输入都不匹配的条目。对于这样的条目,应使用由单个字符 - 组成的名称。
2.16.2. 查找一个网络组
Looking up one Netgroup
网络组的查找函数与所有其他系统数据库处理函数有点不同。由于单个网络组可以包含许多条目,因此需要两步过程。首先选择一个网络组,然后可以遍历该网络组中的所有条目。这些函数在 netdb.h 中声明。
函数:int setnetgrent (const char *netgroup)
Preliminary: | MT-Unsafe race:netgrent locale | AS-Unsafe dlopen plugin heap lock | AC-Unsafe corrupt lock fd mem | See POSIX Safety Concepts.
对该函数的调用将初始化库的内部状态,以允许对 getnetgrent 的后续调用迭代网络组中名为 netgroup 的所有条目。
当调用成功时(即存在同名的网络组),返回值为 1。当返回值为 0 时,不知道同名的网络组或发生其他错误。
重要的是要记住,迭代网络组只有一个状态。即使程序员使用 getnetgrent_r 函数,结果也不是真正可重入的,因为一次只能处理一个网络组。如果程序需要同时处理多个网络组,她必须使用外部锁定来保护它。这个问题是在 SunOS 的原始网络组实现中引入的,由于我们必须保持兼容,因此无法更改此问题。
其他一些函数也使用网络组状态。目前这些是 innetgr 函数和 NSS 实现的 compat 服务部分的实现部分。
函数:int getnetgrent (char **hostp, char **userp, char **domainp)
Preliminary: | MT-Unsafe race:netgrent race:netgrentbuf locale | AS-Unsafe dlopen plugin heap lock | AC-Unsafe corrupt lock fd mem | See POSIX Safety Concepts.
此函数返回当前选定网络组的下一个未处理条目。字符串指针,其中地址在参数hostp、userp 和domainp 中传递,在成功调用后将包含指向适当字符串的指针。如果下一个条目中的字符串为空,则指针的值为 NULL。返回的字符串指针仅在没有调用与网络组相关的函数时才有效。
如果成功读取下一个条目,则返回值为 1。值 0 表示不存在更多条目或发生内部错误。
函数:int getnetgrent_r (char **hostp, char **userp, char **domainp, char *buffer, size_t buflen)
Preliminary: | MT-Unsafe race:netgrent locale | AS-Unsafe dlopen plugin heap lock | AC-Unsafe corrupt lock fd mem | See POSIX Safety Concepts.
此函数与 getnetgrent 类似,只有一个例外:三个字符串指针 hostp、userp 和 domainp 指向的字符串被放置在从 buffer 开始的 buflen 字节缓冲区中。这意味着即使调用了其他与网络组相关的函数,返回的值也是有效的。
如果成功读取下一个条目并且缓冲区包含足够的空间来放置字符串,则返回值为 1。如果找不到更多条目、缓冲区太小或发生内部错误,则返回 0。
这个函数是一个 GNU 扩展。SunOS libc 中的原始实现不提供此功能。
函数:void endnetgrent (void)
Preliminary: | MT-Unsafe race:netgrent | AS-Unsafe dlopen plugin heap lock | AC-Unsafe corrupt lock fd mem | See POSIX Safety Concepts.
此函数释放所有分配用于处理最后选择的网络组的缓冲区。因此,调用 getnetgrent 返回的所有字符串指针在之后都无效。
2.16.3. 测试网络组成员
Testing for Netgroup Membership
通常不需要扫描整个网络组,因为通常唯一有趣的问题是给定条目是否是所选网络组的一部分。
函数:int innetgr (const char *netgroup, const char *host, const char *user, const char *domain)
Preliminary: | MT-Unsafe race:netgrent locale | AS-Unsafe dlopen plugin heap lock | AC-Unsafe corrupt lock fd mem | See POSIX Safety Concepts.
此函数测试由参数 host、user 和 domain 指定的三元组是否是 netgroup 网络组的一部分。使用此功能的好处是
- 没有其他网络组功能可以使用全局网络组状态,因为使用了内部锁定并且
- 该函数的实现比对其他 set/get/endnetgrent 函数的连续调用更有效。
任何指针主机、用户或域都可以为 NULL,这意味着在此位置可以接受任何值。名称也是如此 - 否则不应匹配任何其他字符串。
如果在网络组中找到与给定三元组匹配的条目,则返回值为 1。如果未找到网络组本身,则返回值为 0,网络组不包含三元组或发生内部错误。