6.2 口令文件
UNIX系统的口令文件是/etc/passwd,包含了多个字段,字段之间使用冒号分隔。这些字段包含在pwd.h中定义的passwd结构中。对于Linux系统,其结构定义为:
/* The passwd structure. */
struct passwd
{
char *pw_name; /* Username. */
char *pw_passwd; /* Password. */
__uid_t pw_uid; /* User ID. */
__gid_t pw_gid; /* Group ID. */
char *pw_gecos; /* Real name. */
char *pw_dir; /* Home directory. */
char *pw_shell; /* Shell program. */
};
root:x:0:0:root:/root:/bin/bash
squid:x:23:23::/var/spool/squid:/dev/null
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
nobody:x:65534:65534:Nobody:/home:/bin/sh
jamza:x:1011:1012::/home/jamza:/bin/bash
- squid登录项的该字段为/dev/null。显然,这是一个设备,不是可执行文件,将其用在此处的目的是,阻止任何人以用户squid的名义登录到该系统。
- 为了阻止一个特定用户登录系统,除使用/dev/null外,还有若干替代方法,使用/bin/nologin命令,打印可定制的出错信息,然后以非0状态阻止登录到系统。
- 使用用户nobody用户名的一个目的是,任何人都可登录至系统,但其用户ID(65534)和组ID(65534)不提供任何特权。该用户ID和组ID只能访问人人皆可读、写的文件。
标准提供了2个获取口令文件项的函数,在给出用户登录名或数值用户ID后,这两个函数就能查看相关项。
#include <pwd.h>
struct passwd *getpwuid(uid_t uid);
struct passwd *getpwnam(const char *name);
/*若成功,返回指针,若出错,返回NULL*/
若要查看的只是登录名或用户ID,上面两个函数满足要求,查看整个口令文件,下面3个函数用于此目的:
#include <pwd.h>
struct passwd *getpwent(void);
/*若成功,返回指针,若出错或者到达文件尾端,返回NULL*/
void setpwent(void);
void endpwent(void);
- 每次调用getpwent函数,则返回口令文件中的下一个记录项。
- 函数setpwent反绕它所使用的口令文件,即将getpwent的读写地址指回口令文件的起始位置,endpwent关闭使用的口令文件。
- 在使用getpwent函数查看完口令文件后,一定要使用endpwent关闭口令文件。
6.3 阴影口令
为了保证系统的安全性,将加密口令存放在另一个被称为阴影口令的文件中,改文件至少包含用户名与加密口令,以及其他与口令相关的信息字段。
在Linux系统中的阴影口令文件为/etc/shadow,其字段对应的数据结构为spwd结构,在shadow.h中定义:
/* Structure of the password file. */
struct spwd
{
char *sp_namp; /* Login name. */
char *sp_pwdp; /* Encrypted password. */
long int sp_lstchg; /* Date of last change. */
long int sp_min; /* Minimum number of days between changes. */
long int sp_max; /* Maximum number of days between changes. */
long int sp_warn; /* Number of days to warn user to change the password. */
long int sp_inact; /* Number of days the account may be inactive. */
long int sp_expire; /* Number of days since 1970-01-01 until account expires. */
unsigned long int sp_flag; /* Reserved. */
};
只有用户登录名和加密口令这两个字段是必须的,其它字段控制口令更改的频率。
访问阴影口令文件的相关函数为:
#include <shadow.h>
struct spwd *getspent(uid_t uid);
struct spwd *getspnam(const char *name);
/*若成功,返回指针,若出错,返回NULL*/
void setspwent(void);
void endspwent(void);
6.4组文件
组文件在Linux系统中为/etc/group,文件中的相关字段对应grp.h文件中定义的group结构:
/* The group structure. */
struct group
{
char *gr_name; /* Group name. */
char *gr_passwd; /* Password. */
__gid_t gr_gid; /* Group ID. */
char **gr_mem; /* Member list. */
};
字段gr_mem是一个指针数组,每个指针指向属于该组的用户名。数组以null结尾。
使用以下的函数来查看组名和组ID值:
#include <grp.h>
struct group *getgrgid(gid_t gid);
struct group *getgrnam(const char *name);
/*若成功,返回指针,若出错,返回NULL*/
若是搜索整个组文件,使用以下3个函数,这些函数类似针对口令文件的3个函数:
#include <grp.h>
struct group *getgrent(void);
/*若成功,返回指针,若出错或者到达文件尾端,返回NULL*/
void setgrent(void);
void endgrent(void);
6.5 附属组ID
使用附属组ID的优点是不必再显式的经常更改组,一个用户会参加多个项目,因此也就要同时属于多个组,此类情况常有。
为了获取与设置附属组ID,使用以下函数:
#include <unistd.h>
int getgroups(int gidsetsize, gid_t grouplist[]);
/*若成功,返回附属组ID数量,若出错,返回-1*/
#include <grp.h> /* on Linux */
int setgroups(int ngroups, const gid_t grouplist[]);
int initgroups(const char *username, gid_t basegid);
函数getgroups将进程所属用户的各个附属组ID写到数组grouplist中,填到该数组的ID数量最多为gidsetsize,函数返回实际填写到数组中的ID数量。
若gidsetsize为0,则函数只返回附属组ID的数量,对数组grouplist不作修改。
setgroups可由超级用户调用以便为调用进程设置附属组ID表。grouplist是组ID数组,而ngroups说明了数组中的元素数。
通常只有initgroups函数调用setgroups,initgroups读整个组文件,然后对username确定其组的成员关系。然后,它调用setgroups,以便为该用户初始化附属组ID表。
6.7其它数据文件
下述数据文件接口都与上述对口令文件和组文件相似:
图中列出的所有数据文件都有get,set,end函数
- get函数:读下一个记录
- set函数:打开相应数据文件,然后反绕该文件,如果希望在相应文件起始处开始处理,则调用此函数
- end函数:关闭相应数据文件
如果数据文件指出某种形式的键搜索,则也提供搜索指定键的记录的例程。例如,对于口令文件,提供两个按键进行搜索的程序:getpwnam寻找具有指定用户名的记录,getpwuid寻找具有指定用户ID的记录。
6.8登录账户记录
utmp文件,它记录当前登录进系统的各个用户;
wtmp文件跟踪各个登录和注销事件。
包含下列结构的一个二进制记录写入这两个文件中:
struct utmp {
char ut_line[8]; /* tty line: "ttyh0", "ttyd0", "ttyp0", ... */
char ut_name[8]; /* login name */
long ut_time; /* seconds since Epoch */
};
登录时,login程序填写这样一个结构,然后将其写入到utmp文件中,同时也将其添写到wtmp文件中。
注销时, init进程将utmp文件中相应的记录擦除(每个字节都填以0 ),并将一个新记录添写到wtmp文件中。
读wtmp文件中的该注销记录,其ut_name字段清除为0。在系统再启动时,以及更改系统时间和日期的前后,都在wtmp文件中添写特殊的记录项。
6.9系统标识
系统定义了uname函数,返回与主机与操作系统相关的信息。
#include <sys/utsname.h>
int uname(struct utsname *name);
/*若成功,返回非负值,若出错,返回-1*/
函数返回信息存放在struct utsname结构体buf中
Linux系统中,结构体utsname定义在/usr/include/sys/utsname.h
/* Structure describing the system and machine. */
struct utsname
{
/* Name of the operating system. */
char sysname[];
/* Name of this node on the network. */
char nodename[];
/* Current release level of this implementation. */
char release[];
/* Current version level of this release. */
char version[];
/* Name of the hardware type the system is running on. */
char machine[];
};
#include <unistd.h>
int gethostname(char *name,int namelen);
成功,返回0,出错,返回-1
gethostname可以用来确定当前的主机名。该名字通常就是TCP/IP网络上主机的名字
name:接收缓冲区。
namelen:接收缓冲区的最大长度
6.10时间和日期例程
time函数返回当前时间和日期
#include <time.h>
time_t time(time_t *calptr);
/*若成功,返回时间值,若出错,返回-1*/
函数clock_gettime可用于获取指定时钟的时间,函数原型为:
#include <sys/time.h>
int clock_gettime(clockid_t clock_id, struct timespec *tsp);
/*若成功,返回0,若出错,返回-1*/
其中时钟通过类型为clockid_t来标识,下表给出了标准值
对特定的时钟设置时间,可以调用clock_settime
#include <sys/time.h>
int clock_settime(clockid_t clock_id, const struct timespec *tsp);
/*若成功,返回0,若出错,返回-1*/
与time函数相比,gettimeofday提供了更高的分辨率(最高位微秒级)。
int gettimeofday(struct timeval *restrict tp,void *restrict tzp);
tzp的唯一合法值是NULL,其他值则将产生不确定的结果。
gettimeofday
返回距1970年1月1日0时0分0秒的时间,将其存放于timeval 结构中。
struct timeval{
long int tv_sec; // 秒数
long int tv_usec; // 微秒数
}
两个函数localtime和gmtime将日历时间转换成以年、月、日、时、分、秒、周日表示的时间,并将这些存放在一个tm结构中。
#include <time.h>
struct tm {
int tm_sec;
int tm_min;
int tm_hour;
int tm_mday; // day of month [1 ~ 31]
int tm_mon; // month since January [0 ~ 11]
int tm_year; // years since 1900
int tm_wday; // days since Sunday [0 ~ 6]
int tm_yday; // days since 1月1日 [0 ~ 365]
int tm_isdst;
};
struct tm *localtime(const time_t *clock);
struct tm *gmtime(const time_t *clock);
localtime和gmtime之间的区别是:localtime将日历时间转换成本地时间(考虑到本地时区和夏时制标志),而gmtime则将日历时间转换成协调统一时间的年、月、日、时、分、秒、周日。
函数mktime以本地时间的年、月、日等作为参数,将其转换成time_t值。
time_t mktime(struct tm* tmptr);
strftime是一个类似于printf的时间值函数,可以通过可用的多个参数来定制产生的字符串。
#include <time.h>
size_t strftime(char *restrict buf, size_t maxsize,
const char* restrict format, const struct tm* restrict tmptr);
size_t strfime_l(char *restrict buf, size_t maxsize,
const char* restrict format, const struct tm *restrict tmptr,
locale_t locale);
2个函数的返回值:若有空间,返回存入数组的字符数。否则,返回0
tmptr参数是要格式化的世界值,由一个指向分解时间值tm结构的指针说明,格式化结构存在一个长度为maxsize个字符的buf数组中,format参数控制时间值的格式。
strptime函数是strftime的反过来版本,把字符串时间转换成分解时间。
#include <time.h>
char *strptime(const char *restrict buf, const char* restrict format,struct tm* restrict tmptr);