4.5 用户信息
除了著名的init程序以外,所有的Linux程序都是由其他程序或用户启动的。你将在第11章中对运行中的程序或进程的交互进行更深入的学习。用户通常是在一个响应他们命令的shell中启动程序。你已经看到,程序能够通过检查环境变量和读取系统时钟来在很大程度上了解它所处的运行环境。程序也能够发现它的使用者的相关信息。
当一个用户要登录进Linux系统时,他有一个用户名和密码。一旦用户名和密码通过验证,用户就可以进入一个shell。从内部机制来说,用户还有一个唯一的用户标识符UID。Linux运行的每个程序实际上都是以某个用户的名义在运行,因此都有一个关联的UID。
你可以对程序进行设置,让它们的运行看上去好像是由另一个用户启动的。当一个程序的SUID位被置位时,它的运行就好像是由该可执行文件的属主启动的。当su命令被执行时,程序的运行就好像它是由超级用户启动的,它随后验证用户的访问权限,将UID改为目标账户的UID值并执行该账户的登录shell。采用这种方式还可以允许一个程序的运行就好像是由另一个用户启动的,它经常被系统管理员用来执行一些维护任务。
既然UID是用户身份的关键,我们就从它开始吧。
UID有它自己的类型——uid_t,它定义在头文件sys/types.h中。它通常是一个小整数。有些UID是系统预定义的,其他的则是系统管理员在添加新用户时创建的。一般情况下,用户的UID值都大于100。
getuid函数返回程序关联的UID,它通常是启动程序的用户的UID。
getlogin函数返回与当前用户关联的登录名。
系统文件/etc/passwd包含一个用户账号数据库。它由行组成,每行对应一个用户,包括用户名、加密口令、用户标识符(UID)、组标识符(GID)、全名、家目录和默认shell。下面是一个示例行:
如果编写一个程序,它能确定启动它的用户的UID,那么你就可以对它进行扩展,让它查找密码文件以找到用户的登录名和全名。但我们并不推荐这种做法,因为为了提高系统的安全性,现代的类UNIX系统都不再使用简单的密码文件了。许多系统,包括Linux,都有一个使用shadow密码文件的选项,原来的密码文件中不再包含任何有用的加密口令信息(这些信息通常存放在/etc/shadow文件中,这是一个普通用户不能读取的文件)。为此,人们定义了一组函数来提供一个标准而又有效的获取用户信息的编程接口:
密码数据库结构passwd定义在头文件pwd.h中,它包含表4-4中的成员。
表4-4
有些UNIX系统可能对用户全名字段使用一个不同的名字。在某些系统(如Linux)上,它是pw_gecos,而在其他系统上,它是pw_comment。这就意味着,我们不能对它给出一个统一的用法。
getpwuid和getpwnam函数都返回一个指针,该指针指向与某个用户对应的passwd结构。这个用户通过getpwuid的UID参数或通过getpwnam的用户登录名参数来确定。出错时,它们都返回一个空指针并设置errno。
实验 用户信息
下面的程序user.c从密码数据库中提取出一些用户信息:
它给出如下的输出,在不同的Linux和UNIX版本中,输出结果可能会稍有差异:
实验解析
这个程序先调用getuid获得当前用户的UID,再把这个UID用在getpwuid函数中来获得密码文件中保存的详细信息。此外,我们还演示了通过在getpwnam中给出用户名root来获得用户信息的方法。
如果查看Linux的源代码,你就能在id命令的源代码中看到另一个使用getuid函数的例子。
如果要扫描密码文件中的所有信息,你可以使用getpwent函数。它的作用是依次取出文件数据项:
getpwent函数依次返回每个用户的信息数据项。当到达文件尾时,它返回一个空指针。如果已经扫描了足够多的数据项,你可以使用endpwent函数来终止处理过程。setpwent函数重置读指针到密码文件的开始位置,这样下一个getpwent调用将重新开始一个新的扫描。这些函数的操作方式与我们在第3章讨论的目录扫描函数opendir、readdir和closedir非常相似。
(有效的和实际的)用户和组标识符还可以被其他一些不太常用的函数获得:
组标识符和有效用户标识符的详细资料请参考系统的手册页,虽然你可能会发现自己根本不需要对它们进行处理。
只有超级用户才能调用setuid和setgid函数。