Linux——用户和组

写在前面:此系列主要参考自UNIX系统编程手册,将会有大量demo。
书籍链接:微云链接

密码文件:/etc/passwd

在Linux中,每个登录的用户都有一个与之相关的数值型用户标识符。用户可以隶属于一个或多个组。而每个组也都拥有唯一的名称和一个组标识符。这个表示符主要有两个用途:其一、确定系统资源所有权;其二、对赋予进程访问上述资源的权限加以控制

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

# 使用以下命令查看/etc/passwd
vim /etc/passwd

在这里插入图片描述
我们这提取一行来分析:

ik:x:1000:1000:ik,,,:/home/ik:/bin/bash

这里总共有七个字段:

  • 登录名: 登录Linux系统的失手,用户输入的名称。与一个数字用户标识符相对应,当我们用 ls -l去显示文件的时候,会显示出此用户名。
    在这里插入图片描述

  • 经过加密的密码: 这个字段包含的是经过加密处理的密码,长度为13个字符。如果密码字段中包含了任何其他字符串,或者或字符串长度超过了13个字符,将禁止此账户登录。因为此类字符串不能代表一个经过加密的有效密码。如果说启用了shadow密码。系统将不会解析此字符串,而这里显示“x”来替代(显然,博主这里是开启了shadow)。而经过加密的密码实际存储在shadow密码文件中。

# 对于/etc/shadow文件中密码:
ik:$6$mn9YRhx1$kGkht1N0d5mde6p9gIymUQiX9N5yCx0fJo3OjVTnYgyRT.F9x5EiXzzlKfYf/Jcrn
  • 用户ID: 用户的数值型ID,范围0~65535。如果该字段值为0,那么相应账户即root账户。
  • 组ID: 用户属祖中首选数组的数值型ID。
  • 注释: 该字段存放关于用户的描述性文件。可以用finge命令查看
ik@ik-virtual-machine:~$ finger ik
Login: ik             			Name: ik
Directory: /home/ik                 	Shell: /bin/bash
On since Mon Mar 29 18:30 (CST) on :0 from :0 (messages off)
No mail.
No Plan.
  • 主目录: 用户登陆后所处的初始路径
  • 登录shell: 一旦用户登录,便交由该程序控制

shadow密码文件:/etc/shadow

刚刚我们提到了一个shadow的概念。

为什么要有这个概念呢?

以前,Unix都在/etc/passwd中维护所有用户信息,包括加密的密码。这无疑带来一个问题:由于许多非特权级别系统工具需要读取密码文件中的其他信息,密码文件因此不得不对所有用户开放可读权限。这无疑是具有安全风险的。

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

刚刚我们也能看到:其实shadow密码文件一般可以分为三部分:登录名、经过加密的密码,以及其他若干与安全性相关的字段

组文件:/etc/group

组概念的出现其实也是为了文件权限访问的管理

比如说,我这个文件对组 A 可读,那么组 A 的所有成员都可以读这个文件。不要去挨个指定文件对哪个用户可读

对用户所属各组信息的定义又两部分组成:一、密码文件中相应用户记录的组ID字段;二、组文件列出的用户所属各组。这个可以去查看/etc/group中对应着一条的记录。
在这里插入图片描述
我们但列出一条来看:

dip:x:30:ik
  • 组名: 组名称
  • 经过加密的密码: 组密码属于非强制,现在很少有组有密码了。不过洗染可以设置密码,使用gpasswd命令设置
  • 组ID: 该组的数值型ID
  • 用户列表: 该组用户的名称

程序获取用户和组的信息

从密码文件获取记录: getpwnam 和 getpwuid

以下着两个库函数都是从密码文件中获取记录,返回一个

#include <pwd.h>

struct passwd *getpwnam(const char* name);
/*
* 返回值:成功返回一个指向passwd结构的指针,失败返回NULL
* name:用户的登录名
*/

struct passwd *getpwuid(uid_t uid);
/*
* 返回值:成功返回一个指向passwd结构的指针,失败返回NULL
* uid:用户的id
*/

struct passwd{
	char* pw_name;		//用户名
	char* pw_passwd;	//加密过的密码
	uid_t pw_uid;		//用户id
	gid_t pw_gid;		//组id
	char* pw_gecos;		//用户信息
	char* pw_dir;		//初始工作目录
	char* pw_shell;		//登录shell
};

一个简单的demo:

#include <pwd.h>

#include <iostream>

using namespace std;

int main(int argc, char **argv)
{
	passwd *p = getpwnam("ik");
	if (p == nullptr)
	{
		cout << "not found" << endl;
		exit(0);
	}

	cout << "name: " << p->pw_name << endl;
	cout << "passwd: " << p->pw_passwd << endl;
	cout << "uid: " << p->pw_uid << endl;
	cout << "gid: " << p->pw_gid << endl;
	cout << "shell: " << p->pw_shell << endl;

	passwd *q = getpwuid(1000);
	cout<<"q uid: "<<q->pw_uid<<endl;
	if (p == q)
	{
		cout << "true" << endl;
	}
	else
	{
		cout << "false" << endl;
	}
}

输出:

ik@ik-virtual-machine:~/桌面/test/bin$ ./test1
name: ik
passwd: x
uid: 1000
gid: 1000
shell: /bin/bash
q uid: 1000
false

这里我们可以看到,getpwuidgetpwname均会返回一个指针,指向一个静态分配的结构。另外,如果在passwd文件中未发现匹配记录,那么这两个函数将返回NULL,且不会改变errno。可以用以下代码加以区分:

struct passwd *pwd;

errno = 0;
pwd = getpwname(name);
if(pwd == nullptr)
{
	if(errno == 0)
	{
		//not found
	}
	else
	{
		//erro
	}
}

从组文件获取记录: getgrname 和 getgrgid

#include <grp.h>

struct group *getgrname(const char* name);
/*
* 返回值:成功返回一个指向group的指针 ,失败返回nullptr
* name:组名
*/

struct group *getgrgid(gid_t gid);
/*
* 返回值:成功返回一个指向group的指针 ,失败返回nullptr
* gid:组id
*/

struct group
{
	char *gr_name;			//组名
	char *gr_passwd;		//加密的密码
	gid_t gr_gid;			//组id
	char **gr_rem;			
};

和上面的函数使用方法一样,这里就不写demo了。

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

getpwent 函数从密码文件中逐条返回记录,当没有记录或者出错返回空。一经调用,会自动打开密码文件。当密码文件处理完毕后,可调用endpwent将其关闭。

#include <pwd.h>

struct passwd *getpwent();
/*
* 返回值:成功返回一个指向group的指针,失败返回nullptr
*/

void setpwent();
/*
* 重新返回文件起始处
*/

void endpwent();
/*
* 关闭文件
*/

一个简单的demo:

#include <pwd.h>

#include <iostream>

using namespace std;

int main(int argc, char **argv)
{
	passwd *pwd = nullptr;
	while ((pwd = getpwent()) != nullptr)
	{
		cout << "name: " << pwd->pw_name << " | uid: " << pwd->pw_uid << endl;
	}
	endpwent();
}

输出:

ik@ik-virtual-machine:~/桌面/test/bin$ ./test1
name: ik
passwd: x
uid: 1000
gid: 1000
shell: /bin/bash
0x7f419ea75f20 0x7f419ea75ee0
p == q ?: false
ik@ik-virtual-machine:~/桌面/test/bin$ ./test1
name: ik
passwd: x
uid: 1000
gid: 1000
shell: /bin/bash
q uid: 1000
false
ik@ik-virtual-machine:~/桌面/test/bin$ ./test1
name: root | uid: 0
name: daemon | uid: 1
name: bin | uid: 2
name: sys | uid: 3
name: sync | uid: 4
name: games | uid: 5
name: man | uid: 6
name: lp | uid: 7
name: mail | uid: 8
name: news | uid: 9
name: uucp | uid: 10
name: proxy | uid: 13
name: www-data | uid: 33
name: backup | uid: 34
name: list | uid: 38
name: irc | uid: 39
name: gnats | uid: 41
name: nobody | uid: 65534
name: systemd-network | uid: 100
name: systemd-resolve | uid: 101
name: syslog | uid: 102
name: messagebus | uid: 103
name: _apt | uid: 104
name: uuidd | uid: 105
name: avahi-autoipd | uid: 106
name: usbmux | uid: 107
name: dnsmasq | uid: 108
name: rtkit | uid: 109
name: cups-pk-helper | uid: 110
name: speech-dispatcher | uid: 111
name: whoopsie | uid: 112
name: kernoops | uid: 113
name: saned | uid: 114
name: avahi | uid: 115
name: colord | uid: 116
name: hplip | uid: 117
name: geoclue | uid: 118
name: pulse | uid: 119
name: gnome-initial-setup | uid: 120
name: gdm | uid: 121
name: ik | uid: 1000
name: mysql | uid: 122
name: redis | uid: 123
name: sshd | uid: 124

从shadow密码文件中获取记录: getspnam 和 getspent

#include <shadow.h>

struct spwd* getspnam(const char* name);
/*
* 返回值:返回一个指向spwd结构体的指针,失败返回null
* name:用户名
*/

struct spwd* getspent();
/*
* 返回值:成功返回一个指向spwd结构体的指针,失败返回null
*/

struct spwd
{
	char *sp_namp;		//用户名
	char *sp_pwdp;		//加密后的密码

	long sp_lstchg;		//密码上次被更改的时间(from 1970.1.1)
	
	long sp_min;		//密码改变之间的最小时间间隔
	long sp_max;		//需要更改的最大天数
	long sp_warn;		//后面不常用
	
	long sp_inact;
	long sp_expire;

	unsigned long sp_flag;
};

注意:
访问shadow文件,运行程序需要加sudo,或者在root下运行

密码加密和用户认证

在博主现阶段的开发中,其实用的最多的就是(用户名,密码)认证方式。出于安全原因,Unix系统会采用单向加密算法对密码进行加密,这意味着根据密码的加密形式将无法还原出原始密码。因此,验证候选密码的唯一方式是使用同一算法对其进行加密,并将加密结果与存储与**/etc/shadow**中的密码进行匹配。

#define _XOPEN_SOURCE
#include <unistd.h>

char* crypt(const char *key,const char* salt);
/*
* 返回值:成功返回一个指向静态开辟的字符串的指针,失败返回null
* key:需要加密的密码,最长8字符
* salt:指向一个两字符的字符串,用来干扰算法,使之更难破解,范围[a-zA-Z0-9/.]
*/

注意:
编译时需要连接库:crypt

一个简单的demo:

#define _XOPEN_SOURCE
#include <unistd.h>
#include<shadow.h>

#include <iostream>

using namespace std;

int main(int argc, char **argv)
{
	spwd*  pwd = getspnam("ik");
	if(pwd  == nullptr)
	{
		cout<<"getspanme error!"<<endl;
		exit(0);
	}
	cout<<"shadow name: "<<pwd->sp_namp<<endl;
	cout<<"shadow passwd: "<<pwd->sp_pwdp<<endl;

	cout<<"before crypt: 123456"<<endl;
	cout<<"after: "<<crypt("123456","ab")<<endl;
}

输出:

ik@ik-virtual-machine:~/桌面/test/bin$ sudo ./test1
shadow name: ik
shadow passwd: $6$mn9YRhx1$kGkht1N0d5mde6p9gIymUQiX9N5yCx0fJo3OjVTnYgyRT.F9x5EiXzzlKfYf/JcrnFZ/rOIlZNOFsYSDmKkUP.
before crypt: 123456
after: ab01FAX.bQRSU

参考文献

[1] UNIX 系统编程手册(上) 第八章 用户和组
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

shenmingik

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值