APUE03 - 系统数据文件与时间

63 阅读9分钟

/etc/passwd

在 Linux 和 Unix 系统中,/etc/passwd 是一个重要的系统文件,用于存储用户账户的信息。尽管文件名是 passwd,但它并不保存用户密码(密码通常存储在 /etc/shadow 文件中),而是包含用户的基本信息。

/etc/passwd 文件结构

每行代表一个用户账户,字段由冒号 : 分隔。每行的格式如下:

ruby
复制代码
username:password:UID:GID:GECOS:home:shell

字段说明

  1. username:用户名。
  2. password:加密后的密码的占位符,通常是 x 或 ``,表示实际密码存储在 /etc/shadow 中。
  3. UID:用户的唯一标识符(User ID)。
  4. GID:用户主组的唯一标识符(Group ID)。
  5. GECOS:用户的全名或描述信息(可以包括其他信息,如电话号码)。
  6. home:用户的主目录路径。
  7. shell:用户登录后使用的默认 shell。

示例

以下是 /etc/passwd 文件中的一行示例:

johndoe:x:1001:1001:John Doe,,,:/home/johndoe:/bin/bash

在这个示例中:

  • 用户名是 johndoe
  • 密码字段是 x,表示实际密码在 /etc/shadow 中。
  • UID 和 GID 都是 1001
  • GECOS 字段包含了用户的全名。
  • 用户的主目录是 /home/johndoe
  • 默认 shell 是 /bin/bash

这些信息可以使用下面的 api 获取:

getpwuid //根据用户 ID 获取用户账户信息。
getpwent // 读取下一个用户账户信息。
getpwnam // 根据用户名获取用户账户信息。

他们返回一个 passwd 结构体。

passwd 结构体定义

struct passwd {
    char   *pw_name;   // 用户名
    char   *pw_passwd; // 密码的占位符(通常是 x,表示在 /etc/shadow 中)
    uid_t   pw_uid;    // 用户的唯一标识符(User ID)
    gid_t   pw_gid;    // 用户主组的唯一标识符(Group ID)
    char   *pw_gecos;  // 用户的全名或描述信息
    char   *pw_dir;    // 用户的主目录路径
    char   *pw_shell;  // 用户登录后使用的默认 shell
};

字段说明

  1. pw_name:用户名,表示用户的登录名。
  2. pw_passwd:密码字段的占位符。通常为 x,表示密码实际上存储在 /etc/shadow 文件中,以提高安全性。
  3. pw_uid:用户的唯一标识符(UID),在系统中唯一标识用户。
  4. pw_gid:用户主组的唯一标识符(GID),表示该用户所属的主组。
  5. pw_gecos:全名或描述信息,可以包括其他信息(如电话号码)。
  6. pw_dir:用户的主目录路径,表示该用户的默认工作目录。
  7. pw_shell:用户登录后的默认 shell,通常是 /bin/bash/bin/sh 等。

在 Linux 系统中,/etc/passwd 文件曾经用来保存用户的基本信息,包括密码。然而,出于安全考虑,现代的 Linux 系统将密码从 /etc/passwd 移到了更安全的文件中,原因如下:

  1. 可读权限问题/etc/passwd 文件必须对所有用户可读,因为操作系统和很多程序都需要访问其中的用户信息(例如用户 ID、组 ID、用户名等)。如果密码明文或直接可解的哈希值保存在这个文件中,任何用户都可以读取并尝试破解密码,这存在严重的安全风险。
  2. 引入 /etc/shadow 文件:为了保护密码的安全,密码的哈希值现在存储在 /etc/shadow 文件中。这个文件只有超级用户(root)或具备特殊权限的用户才能读取。这样,即使普通用户可以访问 /etc/passwd,他们也无法获取密码的哈希值,从而提高了系统的安全性。
  3. 分离敏感数据:通过将密码信息和其他用户信息分离,操作系统可以更好地保护敏感数据。/etc/passwd 文件保留用户的基础信息,而密码哈希被转移到 /etc/shadow 文件进行更严格的保护。

/etc/group

/etc/group 文件是 Unix 和 Linux 系统中用来定义用户组的配置文件。它是一个纯文本文件,每一行代表一个组,包含以下几个字段,并用冒号 (:) 分隔:

  1. 组名:表示组的名称。
  2. 密码:通常为 x 或为空,表示组密码,几乎很少用到。
  3. GID:组的唯一标识号(Group ID)。
  4. 用户列表:属于该组的用户列表,多个用户用逗号分隔。

例如,/etc/group 文件内容:

root:x:0:
sudo:x:27:user1,user2
users:x:100:
dev:x:101:user3,user4

在这个例子中:

  • root 组的 GID 为 0,没有其他用户。
  • sudo 组的 GID 为 27,成员有 user1 和 user2
  • users 组的 GID 为 100,没有列出成员。
  • dev 组的 GID 为 101,成员有 user3 和 user4

该文件主要用于管理组的成员关系,系统中的很多工具都会参考这个文件进行权限控制。

获取信息的 api:

getgrnam
getgrgid

返回一个 group 结构体:

struct group {
    char   *gr_name;    // 组名(字符串)
    char   *gr_passwd;  // 组密码(不常用,通常为占位符 "x")
    gid_t  gr_gid;      // 组 ID(GID,Group ID)
    char   **gr_mem;    // 指向用户成员列表的指针数组
};

结构体成员说明:

  1. gr_name:指向一个字符串,表示组的名称。
  2. gr_passwd:指向组密码的字符串(通常不使用,很多系统都用 x 或者 `` 占位)。
  3. gr_gid:表示组的 ID,类型是 gid_t,它是一个整数类型。
  4. gr_mem:指向一个以空指针结尾的字符串数组,每个字符串是属于该组的用户名。

/etc/shadow

/etc/shadow 文件是 Linux 和 Unix 系统中存储用户密码及相关信息的文件。为了提高安全性,这个文件只能由超级用户(root)和具有适当权限的进程访问。它包含每个用户的加密密码及其他与密码相关的信息,如密码过期时间、修改时间等。

/etc/shadow 文件的每一行对应一个系统用户,字段用冒号(:)分隔。典型的一行如下所示:

username:password:last_change:min:max:warn:inactive:expire:

字段详细说明:

  1. username(用户名):表示该行对应的系统用户名。
  2. password(加密密码):存储用户的加密密码。如果是空的,用户不需要密码即可登录。如果是* 或 !,表示该账户已被禁用或锁定。
  3. last_change(最后一次密码更改日期):表示从 1970 年 1 月 1 日(Unix 时间的起点)到最后一次更改密码的天数。这是一个整数。
  4. min(最短密码更改间隔):表示两次密码修改之间最少需要的天数。
  5. max(最长密码有效期):表示密码可以使用的最长天数,超过此天数后用户必须更改密码。
  6. warn(密码过期警告天数):表示密码到期前系统警告用户需要更改密码的天数。
  7. inactive(密码失效后的宽限天数):密码过期后,账户失效前允许的天数。如果设置为 1,表示不使用此功能。
  8. expire(账户到期时间):表示账户到期时间,是从 1970 年 1 月 1 日算起的天数。如果设置为 1,表示账户永不过期。

例如,/etc/shadow 文件中的一行可能如下:

user1:$6$YdsC1...$laI7op...:18956:7:90:14:7:20000:
  • user1 是用户名。
  • $6$YdsC1...$laI7op... 是加密后的密码(采用了 SHA-512 哈希算法)。
  • 18956 表示从 1970 年 1 月 1 日起算,用户最后一次更改密码的日期。
  • 7 是密码最短更改间隔(7 天)。
  • 90 是密码最长有效期(90 天)。
  • 14 是密码到期前的警告天数。
  • 7 是密码过期后的宽限天数。
  • 20000 是账户到期时间。

安全性:

/etc/shadow 文件比 /etc/passwd 更安全,因为 /etc/passwd 文件是可读的,而 /etc/shadow 只有 root 和具有超级用户权限的用户才能读取。这是为了防止非授权用户访问加密的密码数据,即使密码经过加密处理,也不应向普通用户公开。

获取信息的 api:

struct spwd *getspnam(const char *name);
struct spwd *getspent(void)// 逐行读取 /etc/shadow 文件的内容

其定义如下:

struct spwd {
    char *sp_namp;   // 用户名
    char *sp_pwdp;   // 加密密码
    long sp_lstchg;  // 上次更改密码的日期(从 1970-01-01 起的天数)
    long sp_min;     // 密码最短使用期限(天数)
    long sp_max;     // 密码最长使用期限(天数)
    long sp_warn;    // 密码到期前的警告天数
    long sp_inact;   // 密码过期后的宽限天数
    long sp_expire;  // 账户到期时间(从 1970-01-01 起的天数)
    unsigned long sp_flag; // 保留字段,未使用
};

可以使用 crypt 函数来校验密码:

char *crypt(const char *key, const char *salt);

拿 $6$YdsC1...$laI7op... 举例,字符串中的 $ 是分隔符,该字符串被分割成3段:

  • 算法 ID
  • 加密后的串

key 传递登录密码, salt 需要传 $6$YdsC1... 才行,不然 crypt 函数默认是 DES 加密。

时间

time()
gmtime()
gmtime_r()
localtime()
localtime_r()
mktime()
strftime()
asctime()
asctime_r()
ctime()
ctime_r()

这些函数其实都是在做一个变换。

time() 函数返回 time_t 类型,在大多数 64 位系统上,time_t 是 long 或 long long 类型,通常是 64 位的有符号整数。

gmtime/localtime 将 time_t 类型转换成 struct tm 类型的指针。

struct tm {
    int tm_sec;   // 秒 [0, 60]
    int tm_min;   // 分 [0, 59]
    int tm_hour;  // 小时 [0, 23]
    int tm_mday;  // 日 [1, 31]
    int tm_mon;   // 月 [0, 11],0 表示 1 月
    int tm_year;  // 年,自 1900 起
    int tm_wday;  // 一周中的第几天 [0, 6],0 表示周日
    int tm_yday;  // 一年中的第几天 [0, 365]
    int tm_isdst; // 夏令时标志,正数表示启用夏令时,0 表示不使用,负数表示未知
};

mktime 将 tm 转 time_t。

strftime 格式化时间。

asctime/ctime 将时间转出可读字符串。

具体函数原型可看man文档。

注意:在 Linux 中,localtime() 函数将时间(通常是从 time() 函数返回的 time_t 类型)转换为本地时间,并返回一个指向 struct tm 结构体的指针。这个指针指向的内存区域是由 C 库内部维护的一个静态对象,因此它的内容可能会被后续调用的时间相关函数(例如再次调用 localtime() 或 gmtime())修改。

需要熟悉一下,linux API 的设计方式,多看文档,文档里面没有提到需要手动释放函数返回的指针,那么就是静态分配方式。

关注我的微信公众号:二手的程序员

20200925-101923-883e.gif

20200925-101923-883e.gif