当前位置: 首页 > news >正文

8. LINUX 用户和组

文章目录

      • 8.1 密码文件:`/etc/passwd`
        • 1. 登录名(Login Name)
        • 2. 经过加密的密码(Encrypted Password)
        • 3. 用户 ID(User ID, UID)
        • 4. 组 ID(Group ID, GID)
        • 5. 注释(GECOS 字段)
        • 6. 主目录(Home Directory)
        • 7. 登录 Shell(Login Shell)
        • 8. Shadow 密码机制
        • 9. 网络环境中的密码管理
        • 10. 示例
        • 11. 总结
      • 8.2 Shadow 密码文件:`/etc/shadow`
        • 引言
        • 1. Shadow 密码文件的作用
        • 2. `/etc/shadow` 文件的结构
        • 3. 示例
        • 4. 安全性和管理
        • 5. Shadow 组文件:`/etc/gshadow`
        • 6. 总结
      • 8.3 组文件:`/etc/group`
        • 1. 组名(Group Name)
        • 2. 经过加密处理的密码(Encrypted Password)
        • 3. 组 ID(Group ID, GID)
        • 4. 用户列表(Members List)
        • 5. 组文件的历史和演变
        • 6. 示例
        • 7. Shadow 组文件:`/etc/gshadow`
        • 8. 总结
      • 8.4 获取用户和组的信息
        • 1. 从密码文件获取记录
          • 1.1 `getpwnam()` 和 `getpwuid()`
          • 1.2 可重入版本:`getpwnam_r()` 和 `getpwuid_r()`
        • 2. 从 shadow 密码文件获取记录
          • 2.1 `getspnam()`
        • 3. 从组文件获取记录
          • 3.1 `getgrnam()` 和 `getgrgid()`
          • 3.2 可重入版本:`getgrnam_r()` 和 `getgrgid_r()`
        • 4. 总结
      • 8.5 密码加密和用户认证
        • 1. `crypt()` 函数
        • 2. 盐值(Salt)
        • 3. 用户认证流程
        • 4. 安全注意事项
        • 5. 示例程序:用户认证
        • 6. 总结

8.1 密码文件:/etc/passwd

在类 Unix 系统中,/etc/passwd 文件是用户账户信息的核心存储位置。每个用户账户在该文件中占据一行,每行包含 7 个字段,字段之间用冒号(:)分隔。以下是这 7 个字段的详细解释:

1. 登录名(Login Name)
  • 描述:这是用户登录系统时必须输入的唯一名称,也称为用户名。它是人类可读的符号标识符,对应于数字用户 ID(UID)。
  • 作用:用于标识用户账户。当使用 ls -l 等命令显示文件的所有权时,会显示登录名而不是数值型用户 ID。
2. 经过加密的密码(Encrypted Password)
  • 描述:该字段通常包含经过加密处理的密码。传统的加密算法是 DES(数据加密标准),生成的加密字符串长度为 13 个字符。现代系统通常使用更安全的算法,如 MD5、SHA-256 或 SHA-512,生成的消息摘要长度不同(例如,MD5 生成的加密字符串长度为 34 个字符)。
  • 特殊值
    • x:如果启用了 shadow 密码机制(这是现代系统的常规做法),该字段通常包含字母 x,表示加密后的密码实际存储在 /etc/shadow 文件中。
    • 空字符串:如果该字段为空,表示该账户无需密码即可登录。
    • 其他非空字符串:如果该字段包含任何其他字符串(特别是长度超过 13 个字符的字符串),则禁止该账户登录,因为这些字符串不能代表有效的加密密码。
3. 用户 ID(User ID, UID)
  • 描述:用户的数值型 ID。每个用户在系统中都有一个唯一的 UID。
  • 特权用户:UID 为 0 的用户具有超级用户权限,通常是 root 账户。
  • 历史背景
    • 在 Linux 2.2 及更早版本中,UID 是 16 位值,范围为 0~65535。
    • 从 Linux 2.4 开始,UID 使用 32 位值,支持更多的用户数。
  • 多记录用户:虽然不常见,但允许同一用户 ID 拥有多条记录,从而使得同一用户 ID 可以拥有多个登录名。这样,多个用户可以使用不同的密码访问相同的资源,并且可以关联不同的组 ID。
4. 组 ID(Group ID, GID)
  • 描述:用户所属的首选组(主要组)的数值型 ID。
  • 作用:确定用户的主要组,影响文件和目录的权限设置。关于用户与组之间的从属关系,更多信息可以在 /etc/group 文件中找到。
5. 注释(GECOS 字段)
  • 描述:该字段存放关于用户的描述性文字,通常包括用户的全名、办公室位置、电话号码等信息。
  • 用途:用于提供用户的额外信息。例如,finger 命令会显示此信息。
6. 主目录(Home Directory)
  • 描述:用户登录后所处的初始路径。该字段的内容会被设置为 HOME 环境变量。
  • 作用:指定用户的个人工作空间,通常包含用户的配置文件、文档等。
7. 登录 Shell(Login Shell)
  • 描述:用户登录后启动的程序。通常是某种 shell(如 bashzsh 等),但也可能是其他程序。
  • 默认值:如果该字段为空,默认的登录 shell 是 /bin/sh(Bourne shell)。
  • 作用:指定用户登录后使用的命令解释器或其他程序。该字段的内容会被设置为 SHELL 环境变量。
8. Shadow 密码机制
  • 描述:为了提高安全性,现代系统通常启用 shadow 密码机制。在这种情况下,/etc/passwd 文件中的密码字段仅包含一个占位符(如 x),而实际的加密密码存储在 /etc/shadow 文件中。
  • 优点/etc/shadow 文件只有超级用户(root)可以读取,因此即使普通用户可以读取 /etc/passwd 文件,也无法看到加密后的密码,从而减少了密码被破解的风险。
9. 网络环境中的密码管理
  • NIS(网络信息系统)和 LDAP(轻型目录访问协议):在分布式环境中,密码信息可能通过 NIS 或 LDAP 进行分发。这些系统允许将密码信息存储在远程服务器上,本地系统通过网络获取这些信息。
  • 透明性:只要应用程序使用标准的用户信息查询函数(如 getpwnam()getpwuid() 等),无论是使用 NIS、LDAP 还是本地文件,对应用程序来说都是透明的。
10. 示例

以下是一个典型的 /etc/passwd 文件的示例行:

john:x:1001:1001:John Doe,,,:/home/john:/bin/bash
  • john:登录名
  • x:占位符,表示加密密码存储在 /etc/shadow
  • 1001:用户 ID(UID)
  • 1001:组 ID(GID)
  • John Doe,,,:注释字段,包含用户的全名和其他信息
  • /home/john:主目录
  • /bin/bash:登录 Shell
11. 总结
  • /etc/passwd:存储用户账户的基本信息,包括登录名、用户 ID、组 ID、主目录和登录 Shell 等。
  • Shadow 密码机制:为了提高安全性,加密后的密码通常存储在 /etc/shadow 文件中,而不是直接存储在 /etc/passwd 中。
  • 网络环境:在分布式系统中,密码信息可以通过 NIS 或 LDAP 进行分发,保持对应用程序的透明性。

通过理解 /etc/passwd 文件的结构和功能,系统管理员可以更好地管理和维护用户账户,确保系统的安全性和稳定性。

8.2 Shadow 密码文件:/etc/shadow

引言

在早期的 Unix 系统中,所有用户信息,包括加密后的密码,都存储在 /etc/passwd 文件中。由于许多非特权程序需要读取 /etc/passwd 文件中的其他信息(如用户名、UID、GID 等),该文件必须对所有用户开放可读权限。这为攻击者提供了机会,他们可以通过暴力破解工具尝试猜测用户的密码。为了增强系统的安全性,shadow 密码机制应运而生。

1. Shadow 密码文件的作用

/etc/shadow 文件的主要作用是将经过加密处理的用户密码从 /etc/passwd 文件中分离出来,并将其存储在一个只有超级用户(root)和特定特权程序可以访问的文件中。这样,即使普通用户可以读取 /etc/passwd 文件,也无法直接看到加密后的密码,从而减少了密码被破解的风险。

2. /etc/shadow 文件的结构

每行代表一个用户账户,包含 9 个字段,字段之间用冒号(:)分隔。以下是这些字段的详细解释:

  1. 登录名(Login Name)

    • 描述:与 /etc/passwd 文件中的登录名相同,用于标识用户账户。
    • 作用:用于匹配 /etc/passwd 文件中的相应记录。
  2. 经过加密的密码(Encrypted Password)

    • 描述:用户密码经过加密后的字符串。现代系统通常使用更安全的算法(如 MD5、SHA-256 或 SHA-512)来加密密码。
    • 特殊值
      • !*:表示该账户已被禁用,无法登录。
      • 空字符串:表示该账户无需密码即可登录(虽然不常见,但某些情况下可能允许)。
    • 加密算法标识:加密字符串的前缀会指示使用的加密算法。例如:
      • $1$ 表示使用 MD5 加密。
      • $5$ 表示使用 SHA-256 加密。
      • $6$ 表示使用 SHA-512 加密。
  3. 上次更改密码的日期(Last Password Change)

    • 描述:表示最后一次更改密码的日期,以自 1970 年 1 月 1 日以来的天数表示。
    • 作用:用于计算密码的过期时间。
  4. 最小密码年龄(Minimum Password Age)

    • 描述:表示用户在多少天后可以再次更改密码。如果设置为 0,则用户可以随时更改密码。
    • 作用:防止用户频繁更改密码。
  5. 最大密码年龄(Maximum Password Age)

    • 描述:表示密码的有效期,即用户必须在多少天内更改密码。超过这个期限,用户将被强制更改密码。
    • 作用:确保密码定期更新,提高安全性。
  6. 密码警告期(Password Warning Period)

    • 描述:表示在密码过期前几天开始提醒用户更改密码。
    • 作用:给用户足够的时间来更改即将过期的密码。
  7. 密码不活动期(Password Inactivity Period)

    • 描述:表示密码过期后,账户在多少天内仍然有效。超过这个期限,账户将被锁定。
    • 作用:防止用户在密码过期后继续使用旧密码。
  8. 账户失效日期(Account Expiration Date)

    • 描述:表示账户的失效日期,以自 1970 年 1 月 1 日以来的天数表示。超过这个日期,账户将被禁用。
    • 作用:用于管理临时账户或有固定期限的账户。
  9. 保留字段(Reserved Field)

    • 描述:目前未使用,保留供将来扩展。
3. 示例

以下是一个典型的 /etc/shadow 文件的示例行:

john:$6$random_salt$encrypted_password:18962:0:99999:7:::
  • john:登录名
  • $6$random_salt$encrypted_password:使用 SHA-512 加密的密码
  • 18962:上次更改密码的日期(自 1970 年 1 月 1 日以来的天数)
  • 0:最小密码年龄(用户可以随时更改密码)
  • 99999:最大密码年龄(密码有效期为 99999 天,几乎无限)
  • 7:密码警告期(在密码过期前 7 天开始提醒用户)
  • ::::剩余字段为空,表示没有设置密码不活动期、账户失效日期和保留字段
4. 安全性和管理
  • 权限控制/etc/shadow 文件的权限通常设置为 0000400,只有超级用户(root)可以读取该文件。普通用户无法直接访问其中的内容。
  • 密码加密:现代系统使用更安全的加密算法(如 SHA-256 或 SHA-512)来加密密码,增加了破解难度。
  • 密码策略:通过设置最小密码年龄、最大密码年龄、密码警告期等字段,系统管理员可以实施严格的密码策略,确保密码的安全性。
  • 账户管理:通过设置账户失效日期,系统管理员可以管理临时账户或有固定期限的账户,确保账户在不再需要时自动失效。
5. Shadow 组文件:/etc/gshadow

类似于 /etc/shadow 文件,/etc/gshadow 文件用于存储组密码和其他与组相关的安全信息。每个组在 /etc/gshadow 文件中占据一行,包含 4 个字段,字段之间用冒号(:)分隔。以下是这些字段的详细解释:

  1. 组名(Group Name)

    • 描述:组的名称,与 /etc/group 文件中的组名相同。
    • 作用:用于标识组。
  2. 经过加密的组密码(Encrypted Group Password)

    • 描述:组密码经过加密后的字符串。如果启用了 shadow 密码机制,组密码会存储在这里,而不是在 /etc/group 文件中。
    • 特殊值
      • !*:表示该组没有设置密码,或者密码已禁用。
      • 空字符串:表示该组没有密码保护。
  3. 管理员列表(Administrators List)

    • 描述:属于该组的管理员用户名列表,之间用逗号分隔。管理员可以管理组成员和组密码。
    • 作用:用于管理组的权限。
  4. 成员列表(Members List)

    • 描述:属于该组的普通用户名列表,之间用逗号分隔。
    • 作用:用于列出该组的所有成员。
6. 总结
  • /etc/shadow:存储用户的加密密码和其他与安全性相关的字段,只有超级用户可以读取。它提高了系统的安全性,防止密码被轻易破解。
  • /etc/gshadow:存储组密码和其他与组相关的安全信息,同样只有超级用户可以读取。
  • 密码加密:现代系统使用更安全的加密算法(如 SHA-256 或 SHA-512)来加密密码,增加了破解难度。
  • 密码策略:通过设置最小密码年龄、最大密码年龄、密码警告期等字段,系统管理员可以实施严格的密码策略,确保密码的安全性。
  • 账户管理:通过设置账户失效日期,系统管理员可以管理临时账户或有固定期限的账户,确保账户在不再需要时自动失效。

通过使用 shadow 密码机制,系统管理员可以更好地保护用户密码,防止未经授权的访问,同时保持系统的灵活性和易管理性。

8.3 组文件:/etc/group

在类 Unix 系统中,/etc/group 文件用于定义用户组及其成员关系。每个用户组在该文件中占据一行,每行包含 4 个字段,字段之间用冒号(:)分隔。以下是这些字段的详细解释:

1. 组名(Group Name)
  • 描述:组的名称,类似于 /etc/passwd 文件中的登录名。它是人类可读的符号标识符,对应于数值型组 ID(GID)。
  • 作用:用于标识用户组,并在命令行工具和程序中引用。
2. 经过加密处理的密码(Encrypted Password)
  • 描述:组密码经过加密后的字符串。这是一个非强制特性,现代系统中很少使用组密码。然而,仍然可以通过 gpasswd 命令为组设置密码。
  • 特殊值
    • x:如果启用了 shadow 密码机制,该字段通常包含字母 x,表示加密后的密码实际存储在 /etc/gshadow 文件中。
    • 空字符串:表示该组没有设置密码保护。
    • 其他字符:如果该字段包含任何其他字符串(如 !*),则表示该组密码已禁用或无效。
  • 作用:用于保护组成员身份,确保只有授权用户可以加入该组。例如,当用户使用 newgrp 命令切换到一个受密码保护的组时,需要提供正确的组密码。
3. 组 ID(Group ID, GID)
  • 描述:组的数值型 ID。每个组在系统中都有一个唯一的 GID。
  • 特权组:GID 为 0 的组通常是 root 组,具有超级用户权限。
  • 历史背景
    • 在 Linux 2.2 及更早版本中,GID 是 16 位值,范围为 0~65535。
    • 从 Linux 2.4 开始,GID 使用 32 位值,支持更多的组数。
  • 作用:用于唯一标识用户组,并在文件和目录的权限设置中使用。
4. 用户列表(Members List)
  • 描述:属于该组的用户名列表,各用户名之间用逗号分隔。这份列表包含的是用户名,而不是用户 ID,因为同一个用户 ID 可能对应多个不同的用户名(尽管这种情况不常见)。
  • 作用:列出该组的所有成员。用户可以通过 groups 命令查看当前 shell 进程所属的组,或者通过 groups <username> 查看特定用户的组成员关系。
5. 组文件的历史和演变
  • 早期 Unix 实现:在早期的 Unix 系统中,一个用户只能从属于一个组。用户的初始属组由 /etc/passwd 文件中的组 ID 字段决定。如果用户想要切换到另一个组,必须使用 newgrp 命令,并且如果目标组受密码保护,还需要提供组密码。
  • 并发多属组:4.2BSD 引入了并发多属组的概念,允许用户同时属于多个组。这一特性随后被 POSIX.1-1990 标准化。采用这种方案后,/etc/group 文件开始列出每个用户所属的其他组,从而实现了更灵活的权限管理。
6. 示例

假设用户 avrusersstaffteach 三个组的成员。那么 /etc/passwd 文件中 avr 的记录可能如下所示:

avr:x:1001:100:Avi Raja,,,:/home/avr:/bin/bash
  • 第 4 个字段 100 表示 avr 的初始属组是 users 组(假设 users 组的 GID 为 100)。

而在 /etc/group 文件中,相关的记录可能如下所示:

users:x:100:avr,jane,mark
staff:x:500:avr,bob
teach:x:600:avr,alice
  • users 组的 GID 为 100,成员包括 avrjanemark
  • staff 组的 GID 为 500,成员包括 avrbob
  • teach 组的 GID 为 600,成员包括 avralice
7. Shadow 组文件:/etc/gshadow

类似于 /etc/shadow 文件用于存储用户的加密密码,/etc/gshadow 文件用于存储组的加密密码和其他与组安全相关的信息。每个组在 /etc/gshadow 文件中占据一行,包含 4 个字段,字段之间用冒号(:)分隔。以下是这些字段的详细解释:

  1. 组名(Group Name)

    • /etc/group 文件中的组名相同,用于标识组。
  2. 经过加密的组密码(Encrypted Group Password)

    • 如果启用了 shadow 密码机制,组密码会存储在这里,而不是在 /etc/group 文件中。
    • 特殊值:
      • !*:表示该组没有设置密码,或者密码已禁用。
      • 空字符串:表示该组没有密码保护。
  3. 管理员列表(Administrators List)

    • 属于该组的管理员用户名列表,之间用逗号分隔。管理员可以管理组成员和组密码。
  4. 成员列表(Members List)

    • 属于该组的普通用户名列表,之间用逗号分隔。
8. 总结
  • /etc/group:存储用户组的基本信息,包括组名、组 ID 和组成员列表。它用于定义用户与组之间的关系,并控制对文件和其他系统资源的访问。
  • /etc/gshadow:存储组的加密密码和其他与组安全相关的信息,只有超级用户(root)可以读取。它提高了系统的安全性,防止未经授权的用户加入受密码保护的组。
  • 并发多属组:现代系统允许用户同时属于多个组,这使得权限管理更加灵活。/etc/group 文件列出每个用户所属的其他组,而用户的初始属组由 /etc/passwd 文件中的组 ID 字段决定。
  • 组密码:虽然组密码在现代系统中不常用,但仍然可以通过 gpasswd 命令为组设置密码,以保护组成员身份。

通过理解 /etc/group 文件的结构和功能,系统管理员可以更好地管理和维护用户组,确保系统的安全性和灵活性。

8.4 获取用户和组的信息

本节介绍的库函数用于从密码文件、shadow 密码文件和组文件中获取单条记录,以及扫描这些文件的所有记录。这些函数可以帮助开发者在程序中获取用户的详细信息、组信息等。

1. 从密码文件获取记录
1.1 getpwnam()getpwuid()

这两个函数用于从 /etc/passwd 文件中获取用户信息。它们返回一个指向 struct passwd 结构的指针,该结构包含用户账户的详细信息。

  • 原型

    #include <pwd.h>struct passwd *getpwnam(const char *name);
    struct passwd *getpwuid(uid_t uid);
    
  • 返回值

    • 成功时返回指向 struct passwd 结构的指针。
    • 如果未找到匹配的记录或发生错误,则返回 NULL
  • struct passwd 结构

    struct passwd {char   *pw_name;   /* 登录名 (用户名) */char   *pw_passwd; /* 加密后的密码(如果未启用 shadow 密码) */uid_t   pw_uid;    /* 用户 ID */gid_t   pw_gid;    /* 组 ID */char   *pw_gecos;  /* 注释字段(用户信息) */char   *pw_dir;    /* 主目录 */char   *pw_shell;  /* 登录 shell */
    };
    
  • pw_gecos 字段

    • 该字段的命名源自早期的 Unix 实现,最初用于与运行 GECOS(通用电器综合操作系统)的计算机进行通信。尽管这一用途早已过时,但其名称得以保留,现在用于存储用户的描述性信息(如全名、办公室位置等)。
  • pw_passwd 字段

    • 仅当未启用 shadow 密码时,该字段才会包含有效的加密密码。如果启用了 shadow 密码,该字段通常包含占位符(如 x),而实际的加密密码存储在 /etc/shadow 文件中。
  • 可重入性

    • getpwnam()getpwuid() 返回的指针指向静态分配的内存,因此它们是不可重入的(not reentrant)。每次调用这些函数都会覆盖之前的结果。此外,struct passwd 结构中的某些字段(如 pw_name)也指向静态分配的内存,这进一步增加了不可重入性的问题。
  • 区分“未找到”和“出错”

    • 根据 SUSv3 规范,如果在 /etc/passwd 文件中未找到匹配的记录,getpwnam()getpwuid() 会返回 NULL,并且不会修改 errno。因此,可以通过检查 errno 来区分“未找到”和“出错”两种情况。
    • 然而,不同实现的行为可能有所不同。例如,某些 Unix 实现会在未找到记录时将 errno 设置为 ENOENTESRCH。为了确保代码的可移植性,建议在使用这些函数时谨慎处理 errno 的值。
  • 示例代码

    #include <stdio.h>
    #include <pwd.h>
    #include <errno.h>int main() {struct passwd *pwd;errno = 0;pwd = getpwnam("john");if (pwd == NULL) {if (errno == 0) {printf("User not found\n");} else {perror("Error");}} else {printf("User found: %s\n", pwd->pw_name);}return 0;
    }
    
1.2 可重入版本:getpwnam_r()getpwuid_r()

为了提高线程安全性和可重入性,SUSv3 定义了可重入版本的函数:

  • 原型

    #include <pwd.h>int getpwnam_r(const char *name, struct passwd *pwd, char *buf, size_t buflen, struct passwd **result);
    int getpwuid_r(uid_t uid, struct passwd *pwd, char *buf, size_t buflen, struct passwd **result);
    
  • 参数

    • nameuid:查询条件(用户名或用户 ID)。
    • pwd:用于存储结果的 struct passwd 结构。
    • buf:用于存储 struct passwd 中各字段指向的其他数据的缓冲区。
    • buflen:缓冲区的大小。
    • result:返回的结果指针,如果成功则指向 pwd,否则为 NULL
  • 返回值

    • 成功时返回 0。
    • 如果缓冲区太小,返回 ERANGE
    • 如果未找到匹配的记录,返回 0 并将 *result 设置为 NULL
    • 如果发生其他错误,返回相应的错误码。
  • 示例代码

    #include <stdio.h>
    #include <pwd.h>
    #include <errno.h>
    #include <unistd.h>int main() {struct passwd pwd, *result;char buf[1024];size_t buflen = sizeof(buf);int ret = getpwnam_r("john", &pwd, buf, buflen, &result);if (ret != 0) {if (ret == ERANGE) {printf("Buffer too small\n");} else {perror("Error");}} else if (result == NULL) {printf("User not found\n");} else {printf("User found: %s\n", result->pw_name);}return 0;
    }
    
2. 从 shadow 密码文件获取记录
2.1 getspnam()

getspnam() 函数用于从 /etc/shadow 文件中获取用户的 shadow 密码信息。

  • 原型

    #include <shadow.h>struct spwd *getspnam(const char *name);
    
  • 返回值

    • 成功时返回指向 struct spwd 结构的指针。
    • 如果未找到匹配的记录或发生错误,则返回 NULL
  • struct spwd 结构

    struct spwd {char   *sp_namp;   /* 登录名 */char   *sp_pwdp;   /* 加密后的密码 */long    sp_lstchg; /* 上次更改密码的日期 */long    sp_min;    /* 最小密码年龄 */long    sp_max;    /* 最大密码年龄 */long    sp_warn;   /* 密码警告期 */long    sp_inact;  /* 密码不活动期 */long    sp_expire; /* 账户失效日期 */unsigned long sp_flag; /* 保留字段 */
    };
    
  • 示例代码

    #include <stdio.h>
    #include <shadow.h>
    #include <errno.h>int main() {struct spwd *sp;sp = getspnam("john");if (sp == NULL) {if (errno == 0) {printf("User not found in shadow file\n");} else {perror("Error");}} else {printf("Shadow password found for user: %s\n", sp->sp_namp);}return 0;
    }
    
3. 从组文件获取记录
3.1 getgrnam()getgrgid()

这两个函数用于从 /etc/group 文件中获取组信息。它们返回一个指向 struct group 结构的指针,该结构包含组的详细信息。

  • 原型

    #include <grp.h>struct group *getgrnam(const char *name);
    struct group *getgrgid(gid_t gid);
    
  • 返回值

    • 成功时返回指向 struct group 结构的指针。
    • 如果未找到匹配的记录或发生错误,则返回 NULL
  • struct group 结构

    struct group {char   *gr_name;   /* 组名 */char   *gr_passwd; /* 加密后的组密码(如果未启用 shadow 组密码) */gid_t   gr_gid;    /* 组 ID */char  **gr_mem;    /* 指向组成员用户名数组的指针(以 NULL 结尾) */
    };
    
  • gr_passwd 字段

    • 仅当未启用 shadow 组密码时,该字段才会包含有效的加密密码。如果启用了 shadow 组密码,该字段通常为空或包含占位符(如 !*),而实际的加密密码存储在 /etc/gshadow 文件中。
  • 可重入性

    • getpwnam()getpwuid() 类似,getgrnam()getgrgid() 也是不可重入的。每次调用这些函数都会覆盖之前的结果。
  • 区分“未找到”和“出错”

    • 根据 SUSv3 规范,如果在 /etc/group 文件中未找到匹配的记录,getgrnam()getgrgid() 会返回 NULL,并且不会修改 errno。因此,可以通过检查 errno 来区分“未找到”和“出错”两种情况。
    • 然而,不同实现的行为可能有所不同。例如,某些 Unix 实现会在未找到记录时将 errno 设置为 ENOENTESRCH。为了确保代码的可移植性,建议在使用这些函数时谨慎处理 errno 的值。
  • 示例代码

    #include <stdio.h>
    #include <grp.h>
    #include <errno.h>int main() {struct group *grp;errno = 0;grp = getgrnam("users");if (grp == NULL) {if (errno == 0) {printf("Group not found\n");} else {perror("Error");}} else {printf("Group found: %s\n", grp->gr_name);}return 0;
    }
    
3.2 可重入版本:getgrnam_r()getgrgid_r()

为了提高线程安全性和可重入性,SUSv3 定义了可重入版本的函数:

  • 原型

    #include <grp.h>int getgrnam_r(const char *name, struct group *grp, char *buf, size_t buflen, struct group **result);
    int getgrgid_r(gid_t gid, struct group *grp, char *buf, size_t buflen, struct group **result);
    
  • 参数

    • namegid:查询条件(组名或组 ID)。
    • grp:用于存储结果的 struct group 结构。
    • buf:用于存储 struct group 中各字段指向的其他数据的缓冲区。
    • buflen:缓冲区的大小。
    • result:返回的结果指针,如果成功则指向 grp,否则为 NULL
  • 返回值

    • 成功时返回 0。
    • 如果缓冲区太小,返回 ERANGE
    • 如果未找到匹配的记录,返回 0 并将 *result 设置为 NULL
    • 如果发生其他错误,返回相应的错误码。
  • 示例代码

    #include <stdio.h>
    #include <grp.h>
    #include <errno.h>
    #include <unistd.h>int main() {struct group grp, *result;char buf[1024];size_t buflen = sizeof(buf);int ret = getgrnam_r("users", &grp, buf, buflen, &result);if (ret != 0) {if (ret == ERANGE) {printf("Buffer too small\n");} else {perror("Error");}} else if (result == NULL) {printf("Group not found\n");} else {printf("Group found: %s\n", result->gr_name);}return 0;
    }
    
4. 总结
  • getpwnam()getpwuid():用于从 /etc/passwd 文件中获取用户信息。返回的指针指向静态分配的内存,因此它们是不可重入的。为了提高线程安全性,可以使用可重入版本 getpwnam_r()getpwuid_r()
  • getspnam():用于从 /etc/shadow 文件中获取用户的 shadow 密码信息。返回的指针同样指向静态分配的内存。
  • getgrnam()getgrgid():用于从 /etc/group 文件中获取组信息。返回的指针指向静态分配的内存,因此它们是不可重入的。为了提高线程安全性,可以使用可重入版本 getgrnam_r()getgrgid_r()
  • 可重入性:由于这些函数返回的指针指向静态分配的内存,因此它们是不可重入的。为了确保线程安全性和避免数据竞争,建议使用可重入版本的函数。
  • 区分“未找到”和“出错”:根据 SUSv3 规范,errno 在未找到记录时应保持不变,但在某些实现中可能会被设置为非零值。为了确保代码的可移植性,建议在使用这些函数时谨慎处理 errno 的值。

通过使用这些函数,开发者可以在程序中方便地获取用户和组的详细信息,从而实现更复杂的功能,如用户认证、权限管理等。

8.5 密码加密和用户认证

在类 Unix 系统中,用户认证通常通过用户名(登录名)和密码的方式进行。为了确保安全性,系统使用单向加密算法对密码进行加密,使得从加密后的密码无法还原出原始密码。因此,验证用户输入的密码时,必须使用相同的加密算法对候选密码进行加密,并将其与存储在 /etc/shadow 文件中的加密密码进行比较。

1. crypt() 函数

crypt() 函数是用于加密密码的核心函数。它接受一个密钥(即密码)和一个盐值(salt),并返回加密后的密码字符串。以下是 crypt() 函数的详细说明:

  • 原型

    #define _XOPEN_SOURCE
    #include <unistd.h>char *crypt(const char *key, const char *salt);
    
  • 参数

    • key:要加密的密码字符串。传统上,key 的长度最多为 8 个字符(对于 DES 算法)。现代实现允许更长的密码。
    • salt:用于扰动加密算法的盐值。salt 是一个两字符的字符串,取自 [a-zA-Z0-9./] 这 64 个字符的集合。salt 的作用是增加加密结果的随机性,使得即使两个用户选择了相同的密码,它们的加密结果也会不同。
  • 返回值

    • 成功时返回指向静态分配的字符串的指针,该字符串包含加密后的密码。
    • 如果发生错误,则返回 NULL
  • 加密算法

    • DES:传统的加密算法,生成 13 个字符的加密字符串。salt 参数的前两个字符用于扰动 DES 算法,使得加密结果更加难以破解。
    • MD5:生成 34 个字符的加密字符串,以 $1$ 开头。这种算法比 DES 更加安全。
    • SHA-256SHA-512:生成 60 个字符的加密字符串,分别以 $5$$6$ 开头。这些算法提供了更高的安全性。
  • 示例

    char *encrypted = crypt("password", "ab");
    printf("Encrypted password: %s\n", encrypted);
    

    假设使用 DES 算法,输出可能是:

    Encrypted password: abCDEF1234567890
    

    如果使用 MD5 算法,输出可能是:

    Encrypted password: $1$ab$abcdef1234567890
    
2. 盐值(Salt)

盐值的作用是增加加密结果的随机性,防止攻击者使用预计算的彩虹表(rainbow table)来破解密码。crypt() 函数使用 salt 参数的前两个字符作为实际的盐值。这意味着,即使两个用户选择了相同的密码,只要它们的盐值不同,加密结果也会不同。

  • 盐值的生成

    • 在创建新用户或更改密码时,passwd 程序会生成一个随机的盐值。
    • 盐值可以是任意两个字符,取自 [a-zA-Z0-9./] 这 64 个字符的集合。
    • 由于每个字符有 64 种可能,两个字符的盐值可以产生 4096 种不同的组合(64 × 64 = 4096)。
  • 盐值的存储

    • 加密后的密码字符串中包含了盐值。例如,DES 算法生成的加密字符串的前两个字符就是盐值。
    • 当验证密码时,可以从已加密的密码中提取盐值,并使用相同的盐值对候选密码进行加密。
3. 用户认证流程

用户认证的基本流程如下:

  1. 读取用户名:程序首先读取用户输入的用户名。
  2. 获取密码记录:使用 getpwnam() 函数从 /etc/passwd 文件中获取用户的密码记录。
  3. 获取 shadow 密码记录:如果启用了 shadow 密码机制,使用 getspnam() 函数从 /etc/shadow 文件中获取用户的加密密码。
  4. 读取用户输入的密码:使用 getpass() 函数读取用户输入的密码。getpass() 函数会屏蔽回显功能,防止密码显示在屏幕上。
  5. 加密用户输入的密码:使用 crypt() 函数对用户输入的密码进行加密,并使用从 /etc/shadow 中提取的盐值。
  6. 比较加密结果:将加密后的用户输入密码与 /etc/shadow 中存储的加密密码进行比较。如果两者匹配,则认证成功;否则,认证失败。
4. 安全注意事项
  • 立即清除明文密码:为了防止恶意用户通过内存转储或其他方式获取未加密的密码,程序应在加密后立即清除明文密码。可以通过将密码字符串中的每个字符设置为 \0 来实现这一点。

    for (char *p = password; *p != '\0'; p++) {*p = '\0';
    }
    
  • 防止内存泄露:即使程序崩溃,也应确保未加密的密码不会泄露。为此,建议使用 mlock() 函数将包含密码的内存页锁定在物理内存中,防止其被换出到交换文件中。

  • 避免使用过时的函数getpass() 函数已被标记为遗留函数(LEGACY),虽然大多数 Unix 实现仍然支持它,但建议使用更安全的替代方案,如 readline()getpasswd()

5. 示例程序:用户认证

以下是一个完整的示例程序,演示如何使用 crypt() 函数进行用户认证。该程序读取用户名和密码,然后验证用户输入的密码是否与 /etc/shadow 中存储的加密密码匹配。

#define _BSD_SOURCE     /* Get getpass() declaration from <unistd.h> */
#define _XOPEN_SOURCE   /* Get crypt() declaration from <unistd.h> */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <limits.h>
#include <pwd.h>
#include <shadow.h>
#include <string.h>
#include <sys/types.h>int main(int argc, char *argv[]) {char *username, *password, *encrypted;struct passwd *pwd;struct spwd *spwd;size_t len;long login_name_max;/* 获取主机系统上用户名字符串的最大长度 */login_name_max = sysconf(_SC_LOGIN_NAME_MAX);if (login_name_max == -1) {login_name_max = 256;  /* 如果无法确定最大长度,使用默认值 */}/* 分配内存用于存储用户名 */username = malloc(login_name_max);if (username == NULL) {perror("malloc");exit(EXIT_FAILURE);}/* 读取用户名 */printf("Username: ");fflush(stdout);if (fgets(username, login_name_max, stdin) == NULL) {exit(EXIT_FAILURE);  /* 退出于 EOF */}/* 去除尾部的换行符 */len = strlen(username);if (username[len - 1] == '\n') {username[len - 1] = '\0';}/* 获取密码记录 */pwd = getpwnam(username);if (pwd == NULL) {fprintf(stderr, "Couldn't get password record\n");exit(EXIT_FAILURE);}/* 获取 shadow 密码记录 */spwd = getspnam(username);if (spwd == NULL && errno == EACCES) {fprintf(stderr, "No permission to read shadow password file\n");exit(EXIT_FAILURE);}/* 使用 shadow 密码记录 */if (spwd != NULL) {pwd->pw_passwd = spwd->sp_pwdp;}/* 读取用户输入的密码 */password = getpass("Password: ");/* 加密用户输入的密码,并立即清除明文密码 */encrypted = crypt(password, pwd->pw_passwd);for (char *p = password; *p != '\0'; p++) {*p = '\0';}if (encrypted == NULL) {perror("crypt");exit(EXIT_FAILURE);}/* 比较加密结果 */if (strcmp(encrypted, pwd->pw_passwd) == 0) {printf("Successfully authenticated: UID=%ld\n", (long)pwd->pw_uid);exit(EXIT_SUCCESS);} else {printf("Incorrect password\n");exit(EXIT_FAILURE);}
}
6. 总结
  • crypt() 函数:用于加密密码的核心函数,接受密钥(密码)和盐值作为参数,返回加密后的密码字符串。支持多种加密算法,如 DES、MD5、SHA-256 和 SHA-512。
  • 盐值(Salt):用于增加加密结果的随机性,防止预计算的彩虹表攻击。盐值通常是从 [a-zA-Z0-9./] 这 64 个字符中随机选择的两个字符。
  • 用户认证流程:包括读取用户名、获取密码记录、读取用户输入的密码、加密密码并进行比较。为了确保安全性,程序应在加密后立即清除明文密码。
  • 安全注意事项:防止内存泄露,避免使用过时的函数,确保未加密的密码不会通过内存转储或其他方式泄露。

通过理解 crypt() 函数的工作原理和用户认证的流程,开发者可以编写更加安全的认证程序,确保系统的安全性。

相关文章:

8. LINUX 用户和组

文章目录 8.1 密码文件&#xff1a;/etc/passwd1. 登录名&#xff08;Login Name&#xff09;2. 经过加密的密码&#xff08;Encrypted Password&#xff09;3. 用户 ID&#xff08;User ID, UID&#xff09;4. 组 ID&#xff08;Group ID, GID&#xff09;5. 注释&#xff08;…...

vue监听中的watch监听(详解)

1、watch 选项用于监听数据的变化并执行相应的回调函数。watch 选项提供了两个重要的属性&#xff1a;deep 和 immediate。1.1、深度监听 (deep: true) 当你需要监听一个对象或数组内部的变化时&#xff0c;可以使用 deep: true。 这会使得 watch 监听器递归地监听对象或数组内…...

微信小程序中 隐藏scroll-view 滚动条 网页中隐藏滚动条

在微信小程序中隐藏scroll-view的滚动条可以通过以下几种方法实现&#xff1a; 方法一&#xff1a;使用CSS隐藏滚动条 在小程序的样式文件中&#xff08;如app.wxss或页面的.wxss文件&#xff09;&#xff0c;添加以下CSS代码来隐藏滚动条&#xff1a; scroll-view ::-webkit…...

K8s Pod OOMKilled,监控却显示内存资源并未打满

1. 问题现象 pod一直重启&#xff0c;通过grafana查看&#xff0c;发现内存使用率并没有100%。 2. 排查过程 2.1 describe查看pod最新一次的状态 可以明显看到&#xff0c;最近一次的重启就是因为内存不足导致的。 2.2 describe 查看node节点状态 找到原因了&#xff0c;原来…...

对话|全年HUD前装将超330万台,疆程技术瞄准人机交互“第一屏”

2024年&#xff0c;在高阶智驾进入快速上车的同时&#xff0c;座舱人机交互也在迎来新的增长点。Chat GPT、AR-HUD、车载投影等新配置都在带来新增量机会。 高工智能汽车研究院监测数据显示&#xff0c;2024年1-10月&#xff0c;中国市场&#xff08;不含进出口&#xff09;乘用…...

【HTML+CSS+JS+VUE】web前端教程-10-列表标签之无序列表

无序列表实现 无序列表是一个项目的列表,此列项目使用粗体圆点(典型的小黑圆圈)进行标记 无序列表始于<ul>标签,每个列表项始于<li>标签。<ul><li>苹果...

基于V2X的无人机与特种车辆战地智能通信:技术融合与实战应用

一、引言 1.1 研究背景与意义 在现代战争的复杂环境中&#xff0c;通信系统的高效与可靠已然成为决定胜负的关键因素。随着军事技术的飞速发展&#xff0c;战争形态发生了深刻变革&#xff0c;作战空间不断拓展&#xff0c;从陆地、海洋、天空延伸至电磁、网络、太空等多维领…...

20250109下载JDK17的方法链接

20250109下载JDK17的方法&链接 2025/1/9 16:20 缘起&#xff1a;编译地面站应用程序QGC&#xff0c;需要安装QT和【旧版本的】JDK17。 当时在网上没有找到JDK17&#xff0c;就安装了比较接近的JDK21。反正最后的QT for Android最后就是没有编译通过。 到底是谁的问题&#…...

杭州铭师堂的云原生升级实践

作者&#xff1a;升学e网通研发部基建团队 公司介绍 杭州铭师堂&#xff0c;是一个致力于为人的全面发展而服务的在线教育品牌。杭州铭师堂秉持“用互联网改变教育&#xff0c;让中国人都有好书读”的使命&#xff0c;致力于用“互联网教育”的科技手段让更多的孩子都能享有优…...

chrome浏览器的更新提示弹窗无法更新Chrome解决方法

使用组策略编辑器 此方法适用于 Windows 系统且系统为专业版及以上版本&#xff0c;家庭版系统没有组策略功能。 按下Win R键&#xff0c;打开 “运行” 对话框&#xff0c;输入gpedit.msc并回车&#xff0c;打开组策略编辑器。 在组策略编辑器中&#xff0c;依次展开 “计算机…...

LLM prompt提示构造案例:语音回复内容;o1思维链

1、语音回复内容 目的&#xff1a; 语音聊天助手的prompt&#xff0c;让大模型来引导聊天内容&#xff0c;简短和友好&#xff0c;从而文字转语音时候也比较高效。 ## 角色设定与交互规则 ### 基本角色 你是用户的好朋友. 你的回答将通过逼真的文字转语音技术阅读. ### 回答规则…...

OceanBase 学习计划全攻略:开启分布式数据库探索之旅

《OceanBase 学习计划全攻略&#xff1a;开启分布式数据库探索之旅》 在当今数字化浪潮汹涌澎湃的时代&#xff0c;数据库作为企业信息存储与管理的核心基础设施&#xff0c;其性能、可靠性和扩展性至关重要。OceanBase 作为一款具有卓越分布式特性的国产数据库&#xff0c;正…...

Linux 虚拟机与windows主机之间的文件传输--设置共享文件夹方式

Linux 虚拟机与windows主机之间的文件传输 设置共享文件夹方式 在虚拟机中打开终端查看是否已经新建完成&#xff0c;到文件夹中找到它看一下&#xff0c;这个位置就能存储东西啦...

React Context用法总结

1. 基本概念 1.1 什么是 Context Context 提供了一种在组件树中共享数据的方式&#xff0c;而不必通过 props 显式地逐层传递。它主要用于共享那些对于组件树中许多组件来说是"全局"的数据。 1.2 基本用法 // 1. 创建 Context const ThemeContext React.createC…...

Linux好用软件

力荐软件 apt-fast:更快速的软件管理安装过程会进入一个图形界面,配置线程数等信息,全部默认即可 sudo add-apt-repository ppa:apt-fast/stable sudo apt-get update sudo apt-get -y install apt-fast 以后安装应用,把apt-get直接替换成apt-fast即可,例如安装vlc sudo…...

【MYSQL】

文章目录 1.DDL 1.DDL --添加字段 ALTER TABLE table_name add COLUMN embed_model VARCHAR(32) NOT NULL COMMENT 名称备注 COLLATE utf8mb4_bin AFTER config_code;--修改字段 ALTER TABLE table_name CHANGE COLUMN column_a column_b VARCHAR(500) NOT NULL COMMENT 配置信…...

webrtc之rtc::ArrayView<const uint8_t>

rtc::ArrayView<const uint8_t> 是 WebRTC&#xff08;或其他基于 rtc 命名空间的库&#xff09;中常见的一个类型&#xff0c;它通常用于表示一块 只读的内存区域&#xff0c;该内存区域由一系列 uint8_t 类型&#xff08;无符号 8 位整数&#xff09;元素组成。 1. rt…...

深入理解 MySQL 的 EXPLAIN 工具

1. 什么是 EXPLAIN 工具&#xff1f; EXPLAIN 是 MySQL 中用来分析 SQL 查询执行计划的命令&#xff0c;它能够显示查询在执行时会如何访问表、使用哪些索引、扫描多少行等信息。通过 EXPLAIN 工具&#xff0c;开发者可以直观地了解查询的执行过程&#xff0c;从而进行针对性的…...

谷歌Google、紫鸟浏览器插件开发

对于跨境电商行业的IT部门来说&#xff0c;经常需要获取各种店铺相关数据&#xff0c;但是仅靠官方提供的接口来获取数据远远不够&#xff0c;这个时候我们就需要插件或者RPA的方式来获取数据。 以下是关于自研紫鸟插件的简单demo&#xff0c;紫鸟浏览器使用的是火狐和谷歌的插…...

HTML 显示器纯色亮点检测工具

HTML 显示器纯色亮点检测工具 相关资源文件已经打包成html等文件&#xff0c;可双击直接运行程序&#xff0c;且文章末尾已附上相关源码&#xff0c;以供大家学习交流&#xff0c;博主主页还有更多Html相关程序案例&#xff0c;秉着开源精神的想法&#xff0c;望大家喜欢&#…...

Win32汇编学习笔记09.SEH和反调试

Win32汇编学习笔记09.SEH和反调试-C/C基础-断点社区-专业的老牌游戏安全技术交流社区 - BpSend.net SEH - structed exception handler 结构化异常处理 跟筛选一样都是用来处理异常的,但不同的是 筛选器是整个进程最终处理异常的函数,但无法做到比较精细的去处理异常(例如处理…...

计算机组成原理(九):乘法器

乘法器原理 乘法器的工作原理可以用二进制乘法来说明。二进制乘法和十进制乘法类似&#xff0c;通过部分积的累加得到结果。 部分积的生成 在二进制乘法中&#xff0c;每一位的乘积是两个二进制数位的 与运算&#xff08;0 0 0&#xff0c;1 0 0&#xff0c;0 1 0&…...

前端开发中常用的插件库

ahooks 一个高质量且可靠的React Hooks库。class-variance-authority&#xff08;CVA&#xff09; 是一个专注于解决CSS类管理问题的库。classnames 是一个在React开发中非常流行的JavaScript工具库&#xff0c;它可以帮助开发者有条件地连接类名字符串。copy-to-clipboard 一个…...

认识+安装ElasticSearch

1. 为什么要学习ElasticSearch? 一般的来说,项目中的搜索功能尤其是电商项目,商品的搜索肯定是访问频率最高的页面之一。目前搜索功能是基于数据库的模糊搜索来实现的&#xff0c;存在很多问题。 1.1 数据库搜索所存在的问题 1.1.1 查询效率较低 由于数据库模糊查询不走索引&…...

Nginx | 解决 Spring Boot 与 Nginx 中的 “413 Request Entity Too Large“ 错误

关注&#xff1a;CodingTechWork 引言 在 Web 开发中&#xff0c;413 Request Entity Too Large 是一种常见的 HTTP 错误&#xff0c;它指示客户端请求的实体&#xff08;例如文件或数据&#xff09;超出了服务器允许的最大大小。对于使用 Spring Boot 和 Nginx 的应用程序来说…...

CAD批量打印可检索的PDF文件

本文虽介绍CAD使用方法&#xff0c;但还是劝告大家尽早放弃使用CAD软件。。。。太TM难用了 当你打开CAD时发现如下一堆图纸&#xff0c;但是不想一个一个打印时。你可以按照下面操作实现自动识别图框实现批量打印。 1.安装批量打印插件 2.安装后打开CAD&#xff0c;输入命令Bp…...

理解Unity脚本编译过程:程序集

https://docs.unity3d.com/Manual/script-compilation.html 关于Unity C#脚本编译的细节&#xff0c;其中一个比较重要的知识点就是如何自定义Assembly。 预定义的assembly 默认情况下&#xff0c;Unity会按照这个规则进行编译。 PhaseAssembly nameScript files1Assembly-…...

Linux-Ubuntu之SPI串行通信陀螺仪和加速度计

Linux-Ubuntu之SPI串口通信陀螺仪和加速度计 一&#xff0c;SPI通信原理二&#xff0c;ICM-20608六轴传感器控制三&#xff0c;代码1.小tip 一&#xff0c;SPI通信原理 SPI&#xff1a;串行全双工通信&#xff0c;最高能达到百MHZ&#xff0c;通常一个主设备跟多个从设备&…...

【C++/控制台】2048小游戏

源代码&#xff1a; #include <iostream> #include <windows.h> #include <stdio.h> #include <math.h> #include <stdlib.h> #include <conio.h> #include <time.h>// #define KEY_DOWN(VK_NONAME) ((GetAsyncKeyState(VK_NONAME)…...

github gitbook写书

github创建新的仓库 在仓库中添加目录 ‘SUMMARY.md # Summary * [简介](README.md)gitbook 新建一个site https://www.gitbook.com/ 注册账号 取名字 一路 next&#xff0c;注意选免费版 最后 gitbook同步到github 你在主页可以看到 刚刚的test网站 点击右上角圈出来…...

鸿蒙中调整应用内文字大小

1、ui Stack() {Row() {ForEach([1, 2, 3, 4], (item: number) > {Text().width(3).height(20).backgroundColor(Color.Black).margin(item 2 ? { left: 8 } : item 3 ? { left: 7 } : { left: 0 })})}.width(97%).justifyContent(FlexAlign.SpaceBetween).padding({ ri…...

欧拉公式和傅里叶变换

注&#xff1a;英文引文机翻&#xff0c;未校。 中文引文未整理去重&#xff0c;如有异常&#xff0c;请看原文。 Euler’s Formula and Fourier Transform Posted byczxttkl October 7, 2018 Euler’s formula states that e i x cos ⁡ x i sin ⁡ x e^{ix} \cos{x} i …...

【C++经典例题】求1+2+3+...+n,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句

&#x1f493; 博客主页&#xff1a;倔强的石头的CSDN主页 &#x1f4dd;Gitee主页&#xff1a;倔强的石头的gitee主页 ⏩ 文章专栏&#xff1a; 期待您的关注 题目描述&#xff1a; 原题链接&#xff1a; 求123...n_牛客题霸_牛客网 (nowcoder.com) 解题思路&#xff1a; …...

Robot---奇思妙想轮足机器人

1 背景 传统机器人有足式、轮式、履带式三种移动方式&#xff0c;每种移动方式都有各自的优缺点。轮式机器人依靠车轮在地面上移动&#xff0c;能源利用率高、移动速度快&#xff0c;但是仅以轮子与地面接触&#xff0c;缺乏越障能力和对复杂地形的适应能力&#xff0c;尤其面对…...

升级 Spring Boot 3 配置讲解 —— 如何处理文件上传下载?

学会这款 &#x1f525;全新设计的 Java 脚手架 &#xff0c;从此面试不再怕&#xff01; 1. 环境准备 在开始之前&#xff0c;确保你已经具备以下环境&#xff1a; JDK 17 或更高版本&#xff08;Spring Boot 3 要求的最低 JDK 版本&#xff09;Maven 或 Gradle 构建工具Spr…...

(四)结合代码初步理解帧缓存(Frame Buffer)概念

帧缓存&#xff08;Framebuffer&#xff09;是图形渲染管线中的一个非常重要的概念&#xff0c;它用于存储渲染过程中产生的像素数据&#xff0c;并最终输出到显示器上。简单来说&#xff0c;帧缓存就是计算机图形中的“临时画布”&#xff0c;它储存渲染操作生成的图像数据&am…...

WebRTC 在视频联网平台中的应用:开启实时通信新篇章

在当今这个以数字化为显著特征的时代浪潮之下&#xff0c;实时通信已然稳稳扎根于人们生活与工作的方方面面&#xff0c;成为了其中不可或缺的关键一环。回首日常生活&#xff0c;远程办公场景中的视频会议让分散各地的团队成员能够跨越地理距离的鸿沟&#xff0c;齐聚一堂共商…...

Python文件操作

文件的编码 文件编码 思考&#xff1a;计算机只能识别&#xff1a;0和1&#xff0c;那么我们丰富的文本文件是如何被计算机识别&#xff0c;并存储在硬盘中呢&#xff1f; 答案&#xff1a;使用编码技术&#xff08;密码本&#xff09;将内容翻译成0和1存入。 编码技术即&am…...

【渗透测试术语总结】

Top 渗透测试常用专业术语 相信大家和我一样&#xff0c;搞不清这些专业名词的区别&#xff0c;所以我来整理一下。 1. POC、EXP、Payload与Shellcode POC&#xff1a;全称 Proof of Concept &#xff0c;中文 概念验证 &#xff0c;常指一段漏洞证明的代码。 EXP&#xf…...

利用ArcGIS快速准确地统计出地块的现状容积率

研究目的 根据建筑.dwg、建筑.dwg Annotation、建筑.dwg Polygon&#xff0c;地籍边界.shp等数据&#xff0c;利用GIS快速准确地统计出地块的现状容积率。 研究思路 加载数据图层&#xff1a;建筑.dwg Polygon、建筑.dwg Annotation&#xff0c;使用空间连接功能把建筑层数数…...

Linux:守护进程

一、套路 直接来&#xff0c;不铺垫了&#xff1b; #include<iostream> #include<string> #include<cstdlib> #include<unistd.h> #include<unistd.h> #include<sys/types.h> #include<sys/stat.h> #include<funtl.h> const …...

《Spring Framework实战》3:概览

欢迎观看《Spring Framework实战》视频教程 Spring Framework 为基于现代 Java 的企业应用程序提供了全面的编程和配置模型 - 在任何类型的部署平台上。 Spring 的一个关键要素是应用程序级别的基础设施支持&#xff1a;Spring 专注于企业应用程序的 “管道”&#xff0c;以便…...

【云商城】高性能门户网构建

第3章 高性能门户网构建 网站门户就是首页 1.OpenResty 百万并发站点架构 ​ 1).OpenResty 特性介绍 ​ 2).搭建OpenResty ​ 3).Web站点动静分离方案剖析 2.Lua语法学习 ​ 1).Lua基本语法 3.多级缓存架构实战 ​ 1).多级缓存架构分析 用户请求网站&#xff0c;最开始…...

IvorySQL 升级指南:从 3.x 到 4.0 的平滑过渡

日前&#xff0c;IvorySQL 4.0 重磅发布&#xff0c;全面支持 PostgreSQL 17&#xff0c;并且增强了对 Oracle 的兼容性。关于 IvorySQL 4.0 的介绍&#xff0c;各位小伙伴可以通过这篇文章回顾&#xff1a;IvorySQL 4.0 发布&#xff1a;全面支持 PostgreSQL 17. 在 IvorySQL…...

GESP202312 四级【小杨的字典】题解(AC)

》》》点我查看「视频」详解》》》 [GESP202312 四级] 小杨的字典 题目描述 在遥远的星球&#xff0c;有两个国家 A 国和 B 国&#xff0c;他们使用着不同的语言&#xff1a;A 语言和 B 语言。小杨是 B 国的翻译官&#xff0c;他的工作是将 A 语言的文章翻译成 B 语言的文章…...

数据库_解决SQL Server数据库log日志过大,清理日志文件方法

SQL Server数据库日志文件过大的原因主要有几个方面&#xff1a; 事务日志记录了所有对数据库进行修改的操作&#xff0c;如插入、更新和删除&#xff0c;这些操作会不断增加日志文件的大小。 长时间运行且未正确结束的事务会持续占用事务日志中的空间&#xff0c;导致日志文…...

Java 的单例模式详解及优化

&#x1f496; 欢迎来到我的博客&#xff01; 非常高兴能在这里与您相遇。在这里&#xff0c;您不仅能获得有趣的技术分享&#xff0c;还能感受到轻松愉快的氛围。无论您是编程新手&#xff0c;还是资深开发者&#xff0c;都能在这里找到属于您的知识宝藏&#xff0c;学习和成长…...

关于 webservice 日志中 源IP是node IP的问题,是否能解决换成 真实的客户端IP呢

本篇目录 1. 问题背景2. 部署gitlab 17.52.1 添加repo源2.2 添加repo源 下载17.5.0的charts包2.3 修改values文件2.3.1 hosts修改如下2.3.2 appConfig修改如下2.3.3 gitlab下的sidekiq配置2.3.4 certmanager修改如下2.3.5 nginx-ingress修改如下2.3.6 <可选> prometheus修…...

[python3]xlrd不支持Excel xlsx文件类型

https://xlrd.readthedocs.io/en/latest/ xlrd is a library for reading data and formatting information from Excel files in the historical .xls format. 解决办法&#xff1a;指定支持的版本1.2.0&#xff0c;pip3 install xlrd1.20 pip3 install xlrd1.2.0 Looking …...

React中createRoot函数原理解读——Element对象与Fiber对象、FiberRootNode与HostRootNode

【2024最新版】React18 核心源码分析教程&#xff08;全61集&#xff09; Element对象与Fiber对象 在 React 中&#xff0c;Element 对象 和 Fiber 对象 是核心概念&#xff0c;用于实现 React 的高效渲染和更新机制。以下是它们的详细解读&#xff1a; 1. Element 对象 定…...