• 【APUE】文件系统 — 系统数据和文件信息


    目录

    一、/etc/passwd

    1.1 getpwnam

    1.2 getpwuid

    二、/etc/group

    2.1 getgrnam

    2.2 getgrgid

    三、/etc/shadow

    补充:何为良好的加密? 

    3.1 getspnam

    3.2 密码校验示例

    3.2.1 crypt

    3.2.2 getpass

    3.2.3 代码实现

    四、时间戳相关

    4.1 time

    4.2 gmtime

    4.3 localtime

    4.4 mktime

    4.5 strftime 

    五、补充说明


    UNIX 系统的正常运作需要使用大量的与系统有关的数据文件。 例如用户登录 UNIX 系统时,或者执行 ls -l 等命令时,都需要从这些数据文件获取必要的数据

    在不同系统环境下,这些数据文件所储存的内容与格式,甚至数据文件名都可能不太一样。标准提供了读取不同系统下数据文件的接口

    后续将简单介绍一下相关数据文件,并介绍获取数据文件内容的接口。注意:所介绍的数据文件依赖于当前系统环境(毕竟如果所有系统环境的数据文件都一样,还需要统一的接口干嘛?直接从文件里读取数据不就得了) 


    一、/etc/passwd

    第一个介绍的数据文件为 /etc/passwd,首先查看该文件:

    vim /etc/passwd

    这是在 Ubuntu 某 LTS 版下的文件内容:

    针对其中某两条(行)进行分析:

    这里用户密码部分以 x 显示,原因后面再介绍 

    下面介绍读取数据文件的接口(接口是为了统一与标准化) 


    1.1 getpwnam

    man 3 getpwnam 

    1. #include
    2. #include
    3. struct passwd * getpwnam(const char *name);

    功能:用于获取 /etc/passwd 文件中内容的接口(通过用户名获取)

    • name — 在 /etc/passwd 文件中查询用户名为 name 的那一行,并将那一行的内容填充至 passwd 结构体并返回

    其中,struct passwd 内容如下:可以看出一个结构体中的各个成员就对应了 /etc/passwd 文件中的一行的不同字段

    1. struct passwd {
    2. char *pw_name; /* username */
    3. char *pw_passwd; /* user password */
    4. uid_t pw_uid; /* user ID */
    5. gid_t pw_gid; /* group ID */
    6. char *pw_gecos; /* user information */
    7. char *pw_dir; /* home directory */
    8. char *pw_shell; /* shell program */
    9. };

    1.2 getpwuid

    man 3 getpwuid

    1. #include
    2. #include
    3. struct passwd *getpwuid(uid_t uid);

    功能:用于获取 /etc/passwd 文件中内容的接口(通过用户 ID 获取)

    • uid — 在 /etc/passwd 文件中查询用户 ID 为 uid 的那一行,并将那一行的内容填充至 passwd 结构体并返回

    代码示例 

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. int main(int argc, char * argv[]) {
    7. if (argc < 3) {
    8. // 检验命令行参数
    9. fprintf(stderr, "Usage: %s <-uid/-n> \n", argv[0]);
    10. exit(-1);
    11. }
    12. struct passwd * pwline;
    13. if (strcmp(argv[1],"-uid") == 0) { // 注意字符串的比较方式
    14. pwline = getpwuid(atoi(argv[2])); // 需要将char*进行转换
    15. if (pwline == NULL) { // 错误检查
    16. printf("Not found or error!\n");
    17. exit(-1);
    18. }
    19. } else if (strcmp(argv[1], "-n") == 0) {
    20. pwline = getpwnam(argv[2]);
    21. if (pwline == NULL) {
    22. printf("Not found or error!\n");
    23. exit(-1);
    24. }
    25. } else {
    26. exit(-1);
    27. }
    28. printf("username: %s, user ID: %d, group ID: %d, user password: %s\n", pwline->pw_name, pwline->pw_uid, pwline->pw_gid, pwline->pw_passwd);
    29. exit(0);
    30. }

    可以看出,能够通过用户名或者用户 ID,并依靠接口获取 /etc/passwd 数据文件中的信息


    二、/etc/group

    第二个介绍的数据文件为 /etc/group,首先查看该文件:

    vim /etc/group

    这是在 Ubuntu 某 LTS 版下的文件内容:

    针对其中某条进行分析:

    这里组密码部分以 x 显示,原因后面再介绍 

    下面介绍读取数据文件的接口(接口是为了统一与标准化) 


    2.1 getgrnam

    man 3 getgrnam

    1. #include
    2. #include
    3. struct group *getgrnam(const char *name);

    功能:用于获取 /etc/group 文件中内容的接口(通过组名获取) 

    • name — 在 /etc/group 文件中查询组名为 name 的那一行,并将那一行的内容填充至 group 结构体并返回 

    其中,struct group 内容如下:可以看出一个结构体中的各个成员就对应了 /etc/group 文件中的一行的不同字段 

    1. struct group {
    2. char *gr_name; /* group name */
    3. char *gr_passwd; /* group password */
    4. gid_t gr_gid; /* group ID */
    5. char **gr_mem; /* NULL-terminated array of pointers to names of group members */
    6. };

    2.2 getgrgid

    man 3 getgrgid

    1. #include
    2. #include
    3. struct group *getgrgid(gid_t gid);

    功能:用于获取 /etc/group 文件中内容的接口(通过组 ID 获取)  

    • gid — 在 /etc/group 文件中查询组 ID 为 gid 的那一行,并将那一行的内容填充至 group 结构体并返回 

    三、/etc/shadow

    取出一行出来分析,这里主要分析第二个字段的内容:这个字段的内容就是加密密码 

    由于 /etc/passwd 文件对所有用户都可读,所以将用户密码存放于 /etc/passwd 是一个安全隐患。因此,现在许多 Linux 系统都使用了 shadow 技术,把真正的加密后的用户密码(也称口令)字段存放到 /etc/shadow 文件中,而在 /etc/passwd 文件的口令字段中只存放一个特殊的字符,例如 “x” 或者 “*”


    补充:何为良好的加密? 

    首先,加密一定要能解密!

    问:hash 是加密吗?

    答:不是,hash 最多算混淆,因为多个不同的原值可能通过 hash 映射到相同的 hash 值,那么从 hash 值恢复出唯一原值是无法实现的,即加密之后无法解密了

    其次,即使相同的原串,经过加密,也应该得到不同的加密后串 

    为什么要这样?如果相同的原串加密后得到相同的加密后串会怎么样?下面讲一个故事

    因此,相同原串能够加密得到不同加密后串,主要目的是为了防止系统管理员监守自盗 


    接下来继续介绍 /etc/shadow 第二个字段的内容,看看 UNIX 系统如何实现加密的

    一个条加密密码由下面三部分组成(缺一不可):

    1. 加密方式
    2. 杂字串
    3. 原串与杂字串进行或运算后,再通过加密方式处理后所得到的串 

    根据加密方式与杂字串,能够反推并运算得到加密前的串,故满足能解密的条件

    即使是相同的原串,由于系统针对不同的用户提供不同杂字串,故即使原串,与不同的杂字串或运算后,再处理得到的串也不同,所得到的加密后串不同,故满足相同原串可得到不同加密后串 

    下面介绍读取数据文件的接口(接口是为了统一与标准化)  


    3.1 getspnam

    man 3 getspnam

    1. #include
    2. struct spwd *getspnam(const char *name);

    功能:用于获取 /etc/shadow 文件中内容的接口(通过登录名获取) 

    • name — 在 /etc/shadow 文件中查询登录名为 name 的那一行,并将那一行的内容填充至 spwd 结构体并返回 

    其中,struct spwd 内容如下:一个结构体中的各个成员对应了 /etc/shadow 文件中的一行的不同字段(以 : 分隔一行的不同字段值)

    1. struct spwd {
    2. char *sp_namp; /* Login name */
    3. char *sp_pwdp; /* Encrypted password */
    4. long sp_lstchg; /* Date of last change (measured in days since 1970-01-01 00:00:00 +0000 (UTC)) */
    5. long sp_min; /* Min # of days between changes */
    6. long sp_max; /* Max # of days between changes */
    7. long sp_warn; /* # of days before password expires to warn user to change it */
    8. long sp_inact; /* # of days after password expires until account is disabled */
    9. long sp_expire; /* Date when account expires(measured in days since 1970-01-01 00:00:00 +0000 (UTC)) */
    10. unsigned long sp_flag; /* Reserved */
    11. };
    1. struct spwd {
    2. char *sp_namp; /* 登录名 */
    3. char *sp_pwdp; /* 加密密码 */
    4. long sp_lstchg; /* 上次更改日期(以 1970-01-01 00:00:00 +0000 (UTC) 后的天数为单位) */ */
    5. long sp_min; /* 两次更改之间的最短间隔天数 */
    6. long sp_max; /* 最大更改间隔天数 */
    7. long sp_warn; /* 密码过期前警告用户更改密码的天数 */
    8. long sp_inact; /* 密码过期后直到账户被禁用的天数 */
    9. long sp_expire; /* 帐户过期日期(以 1970-01-01 00:00:00 +0000 (UTC) 后的天数为单位) */
    10. unsigned long sp_flag; /* 保留 */
    11. };

    需要注意一点:即使是通过这个接口获取 /etc/shadow 中的内容,也需要调用这个接口的用户有能够访问 /etc/shadow 的权限! 


    3.2 密码校验示例

    在此之前需要先补充几个函数

    3.2.1 crypt

    1. #define _XOPEN_SOURCE
    2. #include
    3. char *crypt(const char *key, const char *salt);
    4. Link with -lcrypt

    功能:用于给串进行加密

    • key — 待加密的原串
    • salt — 指定加密方式与杂字串,salt 应该具有 "$id$salt$..." 的形式,id 表示特定加密方式,salt 表示特定杂字串,第三个 $ 符后的部分将被忽略
    • 返回加密后的串(注意包括了三个部分......)
    • 编译的时候要加 -lcrypt;定义宏要在包含头文件之前

    3.2.2 getpass

    1. #include
    2. char *getpass(const char *prompt);

    功能:获取输入密码(想一下登录 root 用户时候的 LINUX 命令行输入密码时候的效果,这个函数就能达到这个效果,本质上是在函数内部暂时关闭了终端的回显)

    • prompt:打印的提示字符
    • 返回获取到的字符串,不包括 '\n'

    3.2.3 代码实现

    1. #define _XOPEN_SOURCE // 要在所有头文件包含前宏定义
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. int main(int argc, char * argv[]) {
    8. if (argc < 2) {
    9. fprintf(stderr, "Usage: %s \n", argv[0]);
    10. exit(-1);
    11. }
    12. struct spwd * shadowline = getspnam(argv[1]); // 根据登录名获取/etc/shadow的一行
    13. char * input_pass = getpass("PassWord:"); // 获取输入原串
    14. char * crypted_pass = crypt(input_pass, shadowline->sp_pwdp); // 获取加密后串,按照指定加密方式和杂字串加密
    15. // 用 与 从/etc/shadow得到的那行的加密串 相同的加密方式和杂字串,对输入原串进行加密
    16. if (strcmp(shadowline->sp_pwdp, crypted_pass) == 0)
    17. puts("ok!");
    18. else
    19. puts("false!");
    20. exit(0);
    21. }


    四、时间戳相关

    首先介绍一下一个时间的表示方式

    UNIX 系统内部对时间的表示方式:time_t 类型的符号整数,表示自 1970 年 1 月 1 日早晨 0 点以来的秒数,又称时间戳

    用户喜欢看到的时间表示方式:char * 类型的字符串,直观明了

    程序员最容易操作的表示方式:struct tm 结构体,结构体中的字段记录了年月日等详细信息

    这几种表示方式之间可以互相转化,相互之间关系如下:

    下面介绍上图中的几个比较常用的函数用法


    4.1 time

    man 2 time

    1. #include
    2. time_t time(time_t *tloc);
    3. // time() returns the time as the number of seconds since the Epoch, 1970-01-01 00:00:00 +0000 (UTC).

    功能:获取当前时间距离 1970 年 1 月 1 日早晨 0 点以来的秒数(即获取时间戳)

    • tloc — 若 tloc 不是 NULL,则获取到的结果将存至 tloc 所指向的位置
    • 函数返回获取到的结果

    因为函数这样设计,那么我们就有两种获取时间戳的方式

    1. #include
    2. #include
    3. #include
    4. int main() {
    5. time_t timestamp;
    6. time(×tamp); // 或者:timestamp = time(NULL);
    7. printf("%ld\n", timestamp);
    8. exit(0);
    9. }

    4.2 gmtime

    man 3 gmtime

    1. #include
    2. struct tm *gmtime(const time_t *timep);

    功能:将时间戳转化为 UTC 时间,并填充至结构体的各个字段

    • timep — 待转换的时间戳
    • struct tm — 待被填充的结构体 
    • 函数返回被填充完毕的结构体 

    被填充的结构体中的字段含义如下:

    1. struct tm {
    2. int tm_sec; /* Seconds (0-60) */
    3. int tm_min; /* Minutes (0-59) */
    4. int tm_hour; /* Hours (0-23) */
    5. int tm_mday; /* Day of the month (1-31) */
    6. int tm_mon; /* Month (0-11) */
    7. int tm_year; /* Year - 1900 */
    8. int tm_wday; /* Day of the week (0-6, Sunday = 0) */
    9. int tm_yday; /* Day in the year (0-365, 1 Jan = 0) */
    10. int tm_isdst; /* Daylight saving time */
    11. };

    因此,我们可以先获取时间戳,再通过时间戳获取这样的一个结构体,从而在结构体中得到我们想要的 UTC 时间信息


    4.3 localtime

    man 3 localtime

    1. #include
    2. struct tm *localtime(const time_t *timep);

    功能:将时间戳转化为本地时间,并填充至结构体的各个字段

    • timep — 待转换的时间戳
    • struct tm — 待被填充的结构体
    • 函数返回被填充完毕的结构体

    被填充的结构体与 gmtime 中介绍的结构体相同 

    gmtime 得到的是 0 时区,把 UTC 时间转换成北京时间的话,需要在年数上加 1900,月份上加 1,小时数加上 8

    localtime 得到的是本地时间,该函数同 gmtime 唯一区别是,在转换小时数不需要加上 8 了


    4.4 mktime

    man 3 mktime

    1. #include
    2. time_t mktime(struct tm *tm);

    功能:将 struct tm 结构体所代表的时间转化为时间戳

    • tm — 用于表示时间的结构体
    • 返回结构体所表示时间的时间戳 

    注意:该函数的形参不是 const,说明可能会对实参内容进行改变:怎么改变呢?

            if structure members are outside their  valid  interval, they  will  be  normalized  (so  that,  for  example,  40 October is changed into 9 November)

    可以用这个特性,计算:从当前时间开始的第1000天是哪一年哪一月哪一日

    1. #include
    2. #include
    3. #include
    4. int main() {
    5. time_t timestamp = time(NULL);
    6. struct tm * tm = localtime(×tamp);
    7. tm->tm_mday += 1000;
    8. mktime(tm); // 自动对tm中的内容进行了标准化处理
    9. printf("%d-%d-%d %d:%d:%d\n", tm->tm_year + 1900, tm->tm_mon, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec);
    10. exit(0);
    11. }

    4.5 strftime 

    man 3 strftime

    1. #include
    2. size_t strftime(char *s, size_t max, const char *format, const struct tm *tm);

    功能:格式化 struct tm 结构体为字符串

    函数根据指定 format 格式化 struct tm,并将结果放入容量为 max 的字符数组中。具体如何通过 format 指定格式详见 man 手册


    五、补充说明

    之前说过,在不同系统环境下,数据文件所储存的内容与格式,甚至数据文件名都可能不太一样。标准提供了读取不同系统下数据文件的接口

    为了说明这一点,我们看看在 macOS 系统下,接口 getpwnam 的内容:

    仔细看,在macOS 下,getpwnam 是从 /etc/master.passwd 文件中获取数据的,而并不像 ubuntu 下从 /etc/passwd 文件中获取数据,这能够说明不同系统下数据文件是不同的

  • 相关阅读:
    文档管理系统如何为 4 家制造商提供竞争优势
    崖山YashanDB启动数据库监控服务操作记录
    Android终端硬件通讯总结(串口通讯、Usb Com、Usb、蓝牙、Wifi)
    【源码+项目部署】Java项目实战_Java进销存管理系统_Java项目开发_Java开源项目_Java课程设计_Java毕业设计_Java课设项目
    rsyslog-日志管理 logrotate-日志轮转
    vim 自动添加脚本信息
    国产音频放大器工作原理以及应用领域
    【大数据】CDC 技术:变化数据捕获
    自己整理博哥爱运维0817-----k8s集成GitLib流水线
    这是你没见过的MindSpore 2.0.0 for Windows GPU版
  • 原文地址:https://blog.csdn.net/qq_41657851/article/details/133655543