首页 > 代码库 > APUE学习笔记:第六章 系统数据文件和信息

APUE学习笔记:第六章 系统数据文件和信息

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);                //返回值:若成功则返回指针,若出错或到达文件结尾则返回NULLvoid 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 found13 }

在程序开始处调用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);        //返回值:若成功则返回指针,若出错或到达文件结尾则返回NULLvoid 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  Solarisint setgroups(int ngroups,const gid_t grouplist[]);#include<grp.h> //on linux and solaris#include<unistd.h> //on freebsd and mac os xint 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