Linux 用户和组编程

在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 		root
passwd 		x
uid 		0
gid 		0
information 	root
directory 	/root
shell 		/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 		root
passwd 		x
gid 		0
members 	

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值