6.1 引言
UNIX系统的正常运行需要使用大量与系统有关的数据文件,针对这些数据文件的可移植接口是本章的主题。本章还介绍了系统标识函数、时间和日期函数
6.2 口令文件
UNIX系统的口令文件包含了下列各字段,这些字段包含在<pwd.h>中定义的passwd结构中
用户名 char *pw_name
加密口令 char *pw_passwd
数值用户ID uid_t pw_uid
数值组ID gid_t pw_gid
注释字段 char *pw_gecos
初始工作目录 char *pw_dir
初始shell char *pw_shell
用户访问类 char *pw_class
下次更改口令时间 time_t pw_change
账户到期时间 time_t pw_expire
由于历史原因,口令文件存储在/etc/passwd中,而且是一个ASCII文件。
linux中,可能有下列四行
root:x:0:0:root:/root:/bin/bash
squid:x:23:23: :/var/spool/squid:/dev/null
nobody:x:65534:65534:Nobody:/home:/bin/sh
sar:x:205:105:Stephen:/home/sar:/bin/bash
关于这些登陆项请注意下列各点:
-通常一个用户名为root的登陆项,其用户ID是0
-加密口令字段包含了一个占位字符(以前加密口令直接放在该字段,但由于安全问题,现在放在另一位置)
-口令文件项中的某些字段可能是口。如果加密口令字段为空,通常意味着该用户没有口令。
-shell字段包含了一个可执行程序名,它被用作该用户的登陆shell。若为空,则取默认值,通常为/bin/sh。(注意,squid登陆项的该字段为/dev/null.显然,这是一个设备,不能
执行,因此将其设于此处的目的是,阻止任何人以用户squid的名义登录到该系统
-为了阻止一个特定用户登陆系统,除使用/dev/null之外,还有若干种替代方法。一种常见的方法是,将/bin/false用作登陆shell。它简单地以不成功(非0)状态终止,该shell将此种状态判断为假。另一种常见的方法是,用/bin/true禁止一个账户。它所做的一切是以成功(0)状态终止。某些系统提供nologin命令,它打印可自定义的出错信息,然后以非0状态终止
-使用nobody用户名的目的是,使任何人都可登陆至系统,但其用户ID(65534)和组ID(65534)不提供任何特权。该用户ID和组ID只能访问人人皆可读写的文件
-提供finger(1)命令的某些UNIX系统支持注释字段中的附加信息。
POSIX.1只定义了两个获取口令文件项的函数。在给出用户登陆名或数值用户ID后,这两个函数就能查询相关项。
#include<pwd.h> struct passwd *getpwuid(uid_t uid); struct passwd *getpwnam(const char *name); //返回值:若成功则返回指针,若出错或到达文件结尾则返回NULL
getpwuid函数由ls(1)程序使用,它将i节点中的数值用户ID映射为用户登陆名。在键入登陆名时,getpwnam函数由login(1)程序使用。
如果要查看整个口令文件,下列三个函数则可用于此种目的
#include<pwd.h> struct passwd *getpwent(void); //返回值:若成功则返回指针,若出错或到达文件结尾则返回NULL void setpwent(void); void endwent(void);
调用getpwent时,它返回口令文件中的下一个记录项。如同上面所述的两个POSIX.1函数一样,它返回一个由它填写好的password结构的指针。每次调用此函数时都重写该结构。在第一次调用该函数时,它打开它所使用的各个文件。
函数setpwent反饶它所使用的文件,endpwent则关闭这些文件。在使用getpwent查看完口令后,一定要调用endpwent关闭这些文件。getpwent知道什么时间它应当打开它所使用的文件(第一次被调用时),但它不知道何时关闭这些文件
实例:6_1 getpwnam函数的实现
1 #include<pwd.h> 2 #include<stddef.h> 3 #include<string.h> 4 struct passwd * getpwnam(const char *name) 5 { 6 struct passwd *ptr; 7 setpwent(); 8 while((ptr=getpwent())!=NULL) 9 if(strcmp(name,ptr->pw_name)==0) 10 break; 11 endpwent(); 12 return(ptr); //ptr is NULL if no match found 13 }
在程序开始处调用setpwent是自我保护性的措施,以便在调用者在此之前已经调用getpwent打开了有关文件的情况下,将有关文件定位到文件开始处。getpwnam和getpwuid调用完成后不应使有关文件仍处于打开状态,所以应调用endpwent关闭它们
6.3 阴影口令
加密口令是经单向加密算法处理过的用户口令副本。因此此算法是单向的,所以不能从加密口令猜测到原来的口令。但人们可以用试探办法猜测口令。。。
为使企图这样做的人难以获得原始资料(加密口令),现在,某些系统将加密口令存放在另一个通常称为阴影口令的文件中。该文件至少要包含用户名和加密口令。
etc/shadow文件中的字段:
用户登陆名 char *sp_namp
加密口令 char *sp_pwdp
上次更改口令以来经过的时间 int sp_lstchg
经过多少天后允许更改 int sp_min
要求更改尚余天数 int sp_max
要求警告天数 int sp_warn
账户不活动之前尚余天数 int sp_inact
账户到期天数 int sp_expire
保留 unsigned int sp_flag
阴影口令文件不是一般用户可以读取的,仅有少数几个程序需要存取加密口令,例如login(1)和passwd(1),这些程序常常是设置用户ID为root的程序。有了阴影口令后,普通口令文件/etc/passwd可由用户自由读取
6.4 组文件
UNIX组文件中的字段包含在<grp.h>中所定义的group结构中
/etc/group文件中的字段:
组名 char *gr_name
加密口令 char *gr_passwd
数值组ID int gr_gid
指向各用户的指针的数组 char **gr_mem
字段gr_mem是一个指针数组,其中每个指针各指向一个属于该组的用户名。该数组以空指针结尾。
可以用下列两个由POSIX.1定义的函数来查看组名或数值组ID
#include<grp.h> struct group *getgrgid(gid_t gid); struct group *getgrnam(const char *name); //两个函数返回值:若成功则返回指针,所出错则返回NULL
如果需要搜索整个组文件,则需使用另外几个函数。下列三个函数类似于针对口令文件的三个函数
#include<grp.h> struct group *getgrent(void); //返回值:若成功则返回指针,若出错或到达文件结尾则返回NULL void setgrent(void); void endgrent(void);
setgrent函数打开组文件(如若它尚未被打开)并反饶它。getgrent函数从组文件中读下一个记录,如若该文件尚未打开则先打开它。endgrent函数关闭组文件
6.5 附加组ID
引入了附加组ID的概念,我们不仅可以属于口令文件记录项中组ID所对应的组,也可属于多达16个另外的组。文件访问权限检查相应被修改为:不仅将进程的有效组ID与文件的组ID相比较,而且也将所有附加组ID与文件组ID相比较。
使用附加组ID的优点是不必再显示地经常更改组。一个用户会参加多个项目,因此也就要同时属于多个组。此类情况是经常有的。
为了获取和设置附加组ID,提供了下列三个函数:
#include<unistd.h> int getgroups(int gidsetsize,gid_t grouplist[]); //返回值:若成功则返回附加组ID数,若出错则返回-1 #include<grp.h> //on linux #include<unistd.h> //on FreeBSD,Mac OS X,and Solaris int setgroups(int ngroups,const gid_t grouplist[]); #include<grp.h> //on linux and solaris #include<unistd.h> //on freebsd and mac os x int initgroups(const char *username,gid_t basegid); //两个函数返回值:若成功则返回0,若出错则返回-1
getgroups将各附加组ID添些到数组grouplist中,该数组中存放的元素最多为gidsetsize个。实际填写到数组中的附加组ID数由函数返回。
作为一个特例,如若gidsetsize为0,则函数值返回附加组ID数,而对数组grouplist则不作修改(这使调用者可以确定grouplist数组的长度,以便进行分配)
setgroups可由超级用户调用以便为调用进程设置附加组ID表。grouplist是组ID数组,而ngroups指定了数组中的元素个数,ngroups的值不能大于NGROUPS_MAX
6.6 实现的区别
在FreeBSD中,阴影口令文件是/etc/master.passwd,可以使用特殊命令编辑该文件,它反过来会从阴影文件爱你产生/etc /passwd的一个副本。另外,还会产生该文件的散列版本。/etc/pwd.db是/etc/passwd的散列版本,/etc/spwd.db是 /etc/master.passwd的散列版本。这些为大型系统提供了更好的性能。
但是Mac OS X只以单用户模式使用/etc/passwd和/etc/master.passwd。在维护系统时,单用户模式通常意味着不能提供任何系统服务。正常运行期间的多用户方式即netinfo目录服务提供对用户和组账户信息的访问。
虽 然Linux和Solaris支持类似的阴影口令接口,但两者之间存在某些微妙的区别。例如,gr_uid在Solaris中定义为int类型,在 Linux中则定义为long int。另一个区别是账户不活动字段。Solaris将其定义为用户上次登录依赖所经过的天数,而Linux则将其定义为到口令过期的尚余天数。
在 很多系统中,用户和组数据库是用网络信息服务(Network Information Service,NIS)实现的。这使管理员可编辑数据库的主副本,然后将它自动分发到组织中的所有服务器上。客户端系统可以联系服务器以查看用户和组的 有关嘻嘻。NIS+和轻量级目录访问协议(Lightweight Directory Access Protocal,LDAP)提供了类似功能。很多系统通过配置文件/etc/nsswitch.conf来控制管理每一类信息的方法。
6.7 其他数据文件
除口令文件和组文件外,UNIX系统还使用很多其他文件。例如,BSD网络软件有一个记录各网络服务器所提供服务的数据文件(/etc/services)
一般情况下,对于每个数据文件至少有三个函数:
(1)get函数:读下一个记录,如果需要,还可打开该文件。
(2)set函数:打开相应数据文件(如果尚未打开),然后反饶该文件。
(3)end函数:关闭相应数据文件
另外,如果数据文件支持某种形式的关键字搜索,则会提供搜索具有指定关键字记录的例程
6.8 登陆账户信息
大多数UNIX系统都提供下列两个数据文件:utmp文件,它记录当前登陆进系统的各个用户,wtmp文件,它跟踪各个登陆和注销时间。
struct utmp{ char ut_line[8]; char ut_name[8]; long ut_time; };
登陆时,login程序添些此类型结构,然后将其写入到utmp文件中,同时也将其填写到wtmp文件中。注销时,init进程将utmp文件中相应的记录擦除(每个字节都填以0),并将一个新纪录填写到wtmp文件中。在wtmp文件的注销记录中,将ut_name字段清零。在系统重新启动时,以及更改系统时间和日期的前后,都在wtmp文件中添些特殊的记录项。
6.9 系统标识
POSIX.1定义了uname函数,它返回与当前主机和操作系统有关的信息
#include<sys/utsname.h> int uname(struct utsname *name); //返回值:若成功则返回非负值,若出错则返回-1
通过该函数的参数想起传递一个utsname结构的地址,然后该函数填写此结构
struct utsname{ char sysname[]; char nodename[]; char release[]; char version[]; char machine[]; };
6.10 时间和日期例程
time函数返回当前时间和日期
#include<time.h> time_h time(time_t *calptr); //返回值:若成功则返回时间值,若出错则返回-1
与time函数相比,gettimeofday提供了更高的分辨率(最高为微秒级)。这对某些应用很重要
#include<sys/time.h> int gettimeofday(struct timeval *restrict tp,void *restrict tzp); 返回值:总是返回0
gettimeofday函数将当前时间存放在tp指向timeval结构中,而该结构存储秒和微秒。
struct timeval{
time_t tv_sec;
long tv_usec;
}
localtime和gmtime将日历时间转换。
#include<time.h> struct tm *gmtime(const time *calptr); struct tm *localtime(const time_t *calptr); //两个函数返回值:指向tm结构的指针
localtime和gmtime之间的区别是:localtime将日历时间转换成本地时间,而gmtime则将日历时间转换称国际标准时间的年、月、、、
函数mktime以本地时间作为参数,将其转换称time_t
#include<time.h> time_t mktime(struct tm *tmptr); 返回值:若成功则返回日历时间,若出错则返回-1
此外还有asctime,ctime,,mktime,strftime
APUE学习笔记:第六章 系统数据文件和信息