unix环境高级编程——第6章系统数据文件和信息

本文详细介绍了UNIX系统中用户和组的管理机制,包括口令文件、阴影口令、组文件等内容,以及如何通过系统函数操作这些文件,实现用户认证、权限控制等功能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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);

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值