User Group

每个用户都拥有一个唯一的用户名和一个与之相关的数值型用户标识符(UID)。用户可以隶属于一个或多个组。每个组也都拥有唯一的一个名称和一个组标识符。

  1. 确定各种系统资源的所有权
  2. 对赋予进程访问上述资源的权限加以控制

密码文件:/etc/passwd

针对系统的每个用户账户,系统密码文件/etc/passwd 会专列一行进行描述。

root:x:0:0:root:/root:/bin/bash

  • 登录名:登录系统时,用户必须输入的唯一名称
  • 加密后的密码【实际密码存储在 /etc/shadow 文件中】
  • 用户 ID (UID)【0:特权用户】
  • 组 ID (GID):用户属组中首选属组的数值型 ID
  • 注释:存放关于用户的描述性文字
  • 主目录:用户登录后所处的初始路径
  • 登录 shell:用户登陆后,交由该程序控制

shadow 密码文件:/etc/shadow

用户的所有非敏感信息存放于”人人可读“的密码文件中,而经过加密处理的密码则由 shadow 密码文件单独维护,仅供具有特权的程序读取。

组文件:/etc/group

组信息由两部分组成:

  1. 密码文件中相应用户记录的组 ID 字段
  2. 组文件列出的用户所属各组

jambit:x:106:claus,felli,frank

  1. 组名:组名称
  2. 加密后的密码
  3. 组 ID (GID)
  4. 用户列表

获取用户和组的信息

从密码文件获取记录

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
#include <pwd.h>

/* A record in the user database.  */
struct passwd
{
  char *pw_name;        /* Username.  */
  char *pw_passwd;        /* Hashed passphrase, if shadow database
                                   not in use (see shadow.h).  */
  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.  */
};

struct passwd *getpwnam(const char* name);
struct passwd *getpwuid(uid_t uid);

函数 getpwnam 和 getpwuid 的作用是从密码文件中获取记录。

SUSv3 规定,如果在 passwd 文件中未发现匹配记录,那么 getpwnam 和 getpwuid 将会返回 NULL,且不会改变 errno。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
struct passwd *pwd;
errno = 0;
pwd = getpwnam(name);
if (pwd == NULL) {
    if (errno == 0){
        // Not Found
    } else {
        // Error
    }
}

从组文件获取记录

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
#include <grp.h>

/* 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.    */
};

struct group *getgrnam(const char* name);
struct group *getgrgid(gid_t gid);

函数 getgrnam 和 getgrgid 的作用是从组文件中获取记录。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
#include <pwd.h>
#include <grp.h>
#include <ctype.h>
#include "ugid_functions.h"

char *username_from_id(uid_t uid)
{
    struct passwd *pwd;
    pwd = getpwuid(uid);
    return (pwd == NULL) ? NULL : pwd->pw_name;
}

uid_t user_id_from_name(const char *name)
{
    struct passwd *pwd;
    uid_t u;
    char *end_ptr;

    if (name == NULL || *name == '\0')
    {
        return -1;
    }
    u = strtol(name, &end_ptr, 10);
    if (*end_ptr == '\0')
    {
        return u;
    }
    pwd = getpwnam(name);
    if (pwd == NULL)
    {
        return -1;
    }
    return pwd->pw_uid;
}

char *group_name_from_id(gid_t gid)
{
    struct group *grp;

    grp = getgrgid(gid);
    return (grp == NULL) ? NULL : grp->gr_name;
}

gid_t group_id_from_name(const char *name)
{
    struct group *grp;
    gid_t g;
    char *endptr;

    if (name == NULL || *name == '\0') /* On NULL or empty string */
        return -1;                     /* return an error */

    g = strtol(name, &endptr, 10); /* As a convenience to caller */
    if (*endptr == '\0')           /* allow a numeric string */
        return g;

    grp = getgrnam(name);
    if (grp == NULL)
        return -1;

    return grp->gr_gid;
}

扫描密码文件和组文件中的所有记录

1
2
3
4
5
#include <pwd.h>

struct passwd *getpwent(void);
void setpwent(void);
void endpwent(void);

函数 getpwent 能够从密码文件中逐条返回记录,当不再有记录【或出错】时返回 NULL。【调用 getpwent 则会自动打开密码文件】当密码文件处理完毕后,可调用 endpwent 将其关闭。

1
2
3
4
5
struct passwd *pwd;
while ((pwd = getpwent()) != NULL) {
    printf("%8-s %5ld\n", pwd->pw_name, (long) pwd->pw_uid);
}
endpwent();

从 shadow 密码文件中获取记录

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <shadow.h>

/* A record in the shadow database.  */
struct spwd
{
   char *sp_namp;             /* Login name.  */
   char *sp_pwdp;             /* Hashed passphrase.  */
   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.  */
};

struct spwd *getspnam(const char* name);
struct spwd *getspent(void);
void setspent(void);
void endspent(void);

函数 getspnam 和 getspent 会返回指向 spwd 类型结构的指针。

密码加密和用户认证

Unix 系统采取单向加密算法对密码进行加密。

1
2
3
4
#define _XOPEN_SOURCE
#include <unistd.h>

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

crypt()算法会接受一个最长可达 8 字符的密钥,并施之一数据加密算法(DES)的一种变体。salt 参数指向一个两字符的字符串,用于扰动 DES 算法。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77

#if !defined(__sun)
#define _BSD_SOURCE /* Get getpass() declaration from <unistd.h> */
#ifndef _XOPEN_SOURCE
#define _XOPEN_SOURCE /* Get crypt() declaration from <unistd.h> */
#endif
#endif
#include <unistd.h>
#include <limits.h>
#include <pwd.h>
#include <shadow.h>
#include "tlpi_hdr.h"

int main(int argc, char *argv[])
{
    char *username, *password, *encrypted, *p;
    struct passwd *pwd;
    struct spwd *spwd;
    Boolean authOk;
    size_t len;
    long lnmax;

    /* Determine size of buffer required for a username, and allocate it */

    lnmax = sysconf(_SC_LOGIN_NAME_MAX);
    if (lnmax == -1) /* If limit is indeterminate */
        lnmax = 256; /* make a guess */

    username = malloc(lnmax);
    if (username == NULL)
        errExit("malloc");

    printf("Username: ");

    fflush(stdout);
    if (fgets(username, lnmax, stdin) == NULL)
        exit(EXIT_FAILURE); /* Exit on EOF */

    len = strlen(username);
    if (username[len - 1] == '\n')
        username[len - 1] = '\0'; /* Remove trailing '\n' */

    /* Look up password and shadow password records for username */

    pwd = getpwnam(username);
    if (pwd == NULL)
        fatal("couldn't get password record");
    spwd = getspnam(username);
    if (spwd == NULL && errno == EACCES)
        fatal("no permission to read shadow password file");

    if (spwd != NULL)                   /* If there is a shadow password record */
        pwd->pw_passwd = spwd->sp_pwdp; /* Use the shadow password */

    password = getpass("Password: ");

    /* Encrypt password and erase cleartext version immediately */

    encrypted = crypt(password, pwd->pw_passwd);
    for (p = password; *p != '\0';)
        *p++ = '\0';

    if (encrypted == NULL)
        errExit("crypt");

    authOk = strcmp(encrypted, pwd->pw_passwd) == 0;
    if (!authOk)
    {
        printf("Incorrect password\n");
        exit(EXIT_FAILURE);
    }

    printf("Successfully authenticated: UID=%ld\n", (long)pwd->pw_uid);

    /* Now do authenticated work... */
    exit(EXIT_SUCCESS);
}
Licensed under CC BY-NC-SA 4.0
Get Things Done
Built with Hugo
Theme Stack designed by Jimmy