Linux 用户和组编程

来源:互联网 发布:光纤传输网络 编辑:程序博客网 时间:2024/06/14 07:35
在Linux中,每个用户都有一个唯一的用户名和用户id。而用户又可以隶属于一个或多个组,每个组也有一个唯一的组名和组id。

1. 密码文件/etc/passwd
首先来看密码文件,对系统中的每个用户账户,密码文件/etc/passwd都会专门有一行对其进行描述,每一行都包含7个字段,用冒号分割,例如:
root:x:0:0:root:/root:/bin/bash
登录名:第一个字段是登录名或者说是用户名。
经过加密的密码:该字段是经过加密处理的密码,这里用的字母'x'来显示。
用户id:用户id,对于root用户,用户id为0。
组id:组id。
注释:存放关于用户的描述性文字。
主目录:用户工作主目录,可以查看HOME环境变量。
登录shell:登录的shell,例如/bin/bash。

对于密码文件的访问,可以通过getpwnam、getpwuid来操作,原型如下:
#include <sys/types.h>#include <pwd.h>struct passwd *getpwnam(const char *name);struct passwd *getpwuid(uid_t uid);

而passwd结构定义如下:

struct passwd {    char   *pw_name;       /* username */    char   *pw_passwd;     /* user password */    uid_t   pw_uid;        /* user ID */    gid_t   pw_gid;        /* group ID */    char   *pw_gecos;      /* user information */    char   *pw_dir;        /* home directory */    char   *pw_shell;      /* shell program */};
其中每个成员对应/etc/passwd中的一个字段,getpwnam和getpwuid返回值都是一样的,只是传递参数不一样,一个是用户名,而另一个是用户id。代码示例如下:
#include <stdio.h>#include <pwd.h>int main(int argc, char *argv[]){        struct passwd *pwd = NULL;        pwd = getpwnam(argv[1]);        if (pwd == NULL) {                perror("getpwnam");                return -1;        }        printf( "name \t\t%s\n"                "passwd \t\t%s\n"                "uid \t\t%d\n"                "gid \t\t%d\n"                "information \t%s\n"                "directory \t%s\n"                "shell \t\t%s\n", pwd->pw_name, pwd->pw_passwd,                                pwd->pw_uid, pwd->pw_gid,                                pwd->pw_gecos, pwd->pw_dir, pwd->pw_shell);        return 0;}
得到root用户信息如下:

name rootpasswd xuid 0gid 0information rootdirectory /rootshell /bin/bash

2. 组文件/etc/group
系统中每个组在/etc/group中都有一条记录,每个记录包含4个字段,用冒号分割,例如:
root:x:0:
组名:组名,与用户登录名类似。
经过加密的组密码:组密码属于非强制性的。
组id:组id号。
用户列表,属于该组的用户列表,之间用逗号分割。

对于root组后面没有用户列表,但是root用户确实属于root组,因为它的组id为0。

对于组文件的访问也有两个函数,原型如下:
#include <sys/types.h>#include <grp.h>struct group *getgrnam(const char *name);struct group *getgrgid(gid_t gid);
返回值为struct group类型指针,struct group定义如下:
struct group {    char   *gr_name;       /* group name */    char   *gr_passwd;     /* group password */    gid_t   gr_gid;        /* group ID */    char  **gr_mem;        /* group members */};
代码示例如下:
#include <stdio.h>#include <grp.h>int main(int argc, char *argv[]){        struct group *grp = NULL;        int i;        grp = getgrnam(argv[1]);        if (grp == NULL) {                perror("getgrnam");                return -1;        }        printf( "name \t\t%s\n"                "passwd \t\t%s\n"                "gid \t\t%d\n", grp->gr_name,                        grp->gr_passwd, grp->gr_gid);        printf("members \t");        for (i = 0; grp->gr_mem[i] != NULL; i++) {                printf("%s ", grp->gr_mem[i]);        }        printf("\n");        return 0;}
root组信息如下:
name rootpasswd xgid 0members 

root组的members成员为空的。

既然这里提到了组的概念,那么我们怎么查看用户属于哪个组呢,linux提供了一个命令,那就是groups命令。groups命令会将用户所属组给列举出来,如果没有指定用户名,那么显示的就是当前登录的用户。

3. 密码文件/etc/shadow
经过加密处理的密码实际上是存储在/etc/shadow文件中的,所以在/etc/passwd文件中看到的用户密码使用字符'x'来表示的。另外/etc/shadow文件是需要具有root用户权限才能访问的,普通用户是没有权限访问的。

对于/etc/shadow文件的访问可以通过getspnam函数,原型如下:
#include <shadow.h>struct spwd *getspnam(const char *name);
注意:需要root权限。

得到的struct spwd结构指针类型定义如下:
struct spwd {    char *sp_namp;     /* Login name */    char *sp_pwdp;     /* Encrypted password */    long  sp_lstchg;   /* Date of last change (measured                          in days since 1970-01-01 00:00:00 +0000 (UTC)) */    long  sp_min;      /* Min # of days between changes */    long  sp_max;      /* Max # of days between changes */    long  sp_warn;     /* # of days before password expires                          to warn user to change it */    long  sp_inact;    /* # of days after password expires                          until account is disabled */    long  sp_expire;   /* Date when account expires (measured                          in days since 1970-01-01 00:00:00 +0000 (UTC)) */    unsigned long sp_flag;  /* Reserved */};
注意:得到的密码也是经过加密处理的密码。

接下来我们用一个实例来看如何通过用户名和密码的方式来认证。

由于安全方面的原因,Linux系统采用的单向加密算法对密码进行加密,也就是说通过加密密码是无法还原出原始密码的。因此,认证的方式只有通过原始密码,然后采用同一加密算法进行加密,并将得到的加密密码与/etc/shadow中的密码进行匹配,完全相同,才表示认证成功。加密算法使用crypt函数,原型如下:
#define _XOPEN_SOURCE       /* See feature_test_macros(7) */#include <unistd.h>char *crypt(const char *key, const char *salt);
代码示例如下:
#define _XOPEN_SOURCE#include <stdio.h>#include <errno.h>#include <unistd.h>#include <stdlib.h>#include <string.h>#include <pwd.h>#include <shadow.h>int main(void){char *username, *password, *encrypted, *p;struct passwd *pwd;struct spwd *spwd;size_t len;int authok;long lnmax;lnmax = sysconf(_SC_LOGIN_NAME_MAX);if (lnmax < 0)/* If limit is indeterminate */lnmax = 256;/* make a guess */username = malloc(lnmax);if (username == NULL)exit(EXIT_FAILURE);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' */pwd = getpwnam(username);if (pwd == NULL) {printf("couldn't get password record\n");exit(EXIT_FAILURE);}spwd = getspnam(username);if (spwd == NULL && errno == EACCES) {printf("no permission to read shadow password file\n");exit(EXIT_FAILURE);}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)exit(EXIT_FAILURE);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);}

注意使用crypt函数时需要连接crypt库,所以在练级时需要加上-lcrypt,编译上面代码命令如下:

gcc -Wall -o test check.c -lcrypt


参考教程:The Linux Programming Interface - A Linux and UNIX System Programming Handbook.pdf

0 0
原创粉丝点击