在Linux内部机制上用户拥有唯一的用户标识符UID
,Linux运行的每个程序实际上都是以某个用户的名义在运行,所以有的程序必须管理员身份才可以运行,我们需要su将UID改为超级用户UID(0)。所以UID时用户身份的关键
。UID的分类如下:
UID范围 | 含义 |
---|---|
0 | 系统管理员,所以当你要让普通用户具有管理员的权限时,将该账号的UID改为0即可 |
1~999 | 系统账号,又分为:1~200:由Linux发行版本自行建立的系统账号,一般用来运行网络服务和后台服务;201~999:若用户由系统账号需求时,进行分配 |
1000~6000 | 给一般用户使用,目前Linux3.10可以支持到2^32-1 |
那我们如何获取用户的UID和登录名呢,系统给我们提供了两个方法getuid,getlogin
:
# include
# include
uid_t getuid(void) //返回程序关联的UID即启动程序的用户UID
char* getlogin(void)//返回当前用户的登录名
uid_t是UID自己的数据类型,定义在头文件sys/types.h中,通常是一个小整数。
我们之前在用户管理命令这一部分说过:系统文件/etc/passwd包含一个用户账号数据库,它由行组成,每行对应一个用户,包括用户名、加密密码占位符,用户标识符UID,组标识符GID,用户全名,家目录和默认shell。如何获取到这些信息呢?系统给我们提供了两个方法getpwuid,getpwnam
:
# include
# include
struct passwd* getpwuid(uid_t uid);//根据UID获取信息
struct passwd* getpwnam(const char* name);//根据用户名获取信息
成功返回一个指向passwd结构的指针,失败返回空指针并设置errno
passwd结构我们称它为密码数据库,存储用户的相信信息,在头文件pwd.h中,主要包含以下信息:
passwd成员 | 说明 |
---|---|
char* pw_name | 用户登录名 |
uid_t pw_uid | UID号 |
gid_t pw_gid | GID号 |
char* pw_dir | 用户家目录 |
char* pw_gecos | 用户全名 |
char* pw_shell | 用户默认shell |
我们可以根据:
struct passwd *pw;
pw=getpwuid(getuid());
pw=getpwnam("xxx")
获取到指向xxx用户的数据库的pw指针,根据结构体指针的指向我们可以获取passwd中的成员,如指针pw->pw_dir获取家目录信息。
可以根据函数获取到用户详细信息,那么也可以通过函数uname
获取到主机的信息,我们可以根据系统调用函数获取主机的详细信息:
# include
int uname(struct utsname* name);
成功返回一个非负整数,失败返回-1,并设置errno指出错误
uname函数把主机信息写入name参数指向的结构,这个结构类型是utsname,它定义在头文件sys/utsname.h中,包含以下信息:
utsname成员 | 说明 |
---|---|
char sysyname[] | 操作系统名 |
char nodename[] | 主机名 |
char release[] | 系统发行级别 |
char version[] | 系统版本号 |
char machine[] | 硬件类型 |
我们可以根据:
struct utsname uts;
uname(&uts)
将主机信息保存在uts结构体中,根据结构体的指向可以获取到主机信息,如uts.nodename获取到主机名。
获取用户信息返回的是一个指向passwd结构体的指针,得到其成员变量需要用【->】符号,获取主机信息得到一个被填充的结构体,得到其成员变量需要用【.】符号。
因为它们的本质不同,一个是指针结构体,一个是结构体。
cd命令可以在shell中切换路径目录,在程序中使用系统调用chdir
也可以进行目录切换:
# include
int chdir(const char* path);
成功返回0,失败返回-1
程序可以通过调用函数获取自己当前的绝对工作目录,函数getcwd
功能和命令pwd一样:
# include
char* getcwd(char* buf,size_t size);
成功返回buf指针,失败返回NULL
getcwd函数把当前绝对目录的名字写到给定的缓冲区buf中,如果目录名的长度超过给出的size即缓冲区长度,那么就失败,返回NULL。
Linux下确定一个特定目录下存放的文件,在shell终端我们可以ls获取,但是在程序中如何通过函数获取呢,Linux提供了一整套的库函数,可以实现对目录进行扫描,获取等操作。
先了解一下DIR结构
,它作为目录操作的基础,被称为目录流的指向,这个结构的指针(DIR*)被用来完成各种目录操作,其使用方法与用来操作普通文件的文件流(FILE*)非常相似。目录数据项本身在dirent结构中返回,该结构也是dirent.h头文件里声明的,这是因为用户不应该直接改动DIR结构中的数据字段。
操作目录流用这三个方法:opendir,readdir,closedir
就可以实现,我们逐一来看一下:
1.opendir
:作用是打开一个目录并建立一个目录流:
# include
# include
DIR* opendir(const char* name);//传入路径
成功返回一个指向DIR结构的指针,该指针用于读取目录数据项,失败返回一个空指针
当目录流使用一个底层文件描述符来访问文件本身,那么如果打开文件过多,opendir可能会失败。
2.readdir
:用于读取已打开目录中的目录项相关信息 :
# include
# include
struct dirent* readdir(DIR *dirp);
成功返回一个指向存储目录项结构体dirent的指针,有错误发生或读取到目录文件尾则返回NULL
结构体dirent
中包含的目录项内容包含以下部分:
成员 | 含义 |
---|---|
ino_t d_ino | 文件的inode节点号 |
char d_name[] | 文件的名字 |
我们可以通过得到的指针,进行结构体成员的访问,如dir->d_name可以获取目录下的文件名。
注意,如果在readdir函数扫描目录的同时还有其他进程在该目录里创建或删除文件,readdir将不保证能够列出该目录里的所有文件和子目录。
3.closedir
:用于关闭一个目录流并释放与之关联的资源:
# include
# include
int closedir(DIR* dirp);
执行成功时返回0,发生错误时返回-1
根据这几个函数我们就可以实现一个类似ls的功能,即打开一个路径下的目录流,读取目录项,输出文件名,最后关闭目录流:下面是该目录下只有一个文件的情况,如果有多个文件,那么需要循环readdir读取目录流,获取信息
。
DIR* dp;//先定义DIR结构指针,指向打开的目录流
struct dirent *entry;//定义保存目录项的结构体指针,指向读取到的目录项结构体
dp=opendir(path);//path为需要打开目录的路径,结果是dp指针指向一个目录流
entry=readdir(dp);//打开dp指针指向的目录流,entry指针指向存储目录项的结构体
printf("%d",entry->d_node);//输出文件节点
printf("%s",entry->d_name);//输出文件名
stat,lstat,fstat
获取;文件类型宏(st_mode) //判断是否为这个类型的文件,是返回1,否则返回0
if(st.st_mode & 文件权限宏) //判断该文件用户是否对应宏描述的权限
具体讲解见文件属性获取这篇博客,就不在此重复讲述了。
在Linux中每个进程都有三个用户标识符,分别为:
UID类型 | 用处 |
---|---|
real uid 真实用户ID | 是登录时的用户ID |
saved uid 已保存的用户ID | 由exec函数保存 |
effective uid 有效用户ID | 在进程运行时的用户ID,可用于文件访问权限检查 |
一般来说,三者相等,但是在某些情况下,我们需要让程序运行时需要特殊的权限,如访问Linux系统的/etc/passwd文件,或让A用户可以以B用户的权限去做某些事,这就需要改变有效用户ID,使用setuid可以进行改变
,让它们暂时获得相关操作权限。
# include
int setuid(uid_t uid);
执行成功返回0,失败返回-1
setuid函数的执行分为以下几个方面:
用户密码为了安全起见,都在一个称为阴影口令的文件/etcshadow中存放着,里面的密码都是加密过的密码,那么我们如何获取到shadow文件中加密后的密码呢?需要用到getspnam
函数:
# include
struct spwd* getspnam(const char* name);//参数传入当前用户名
成功返回指向spwd结构体的指针,失败返回NULL
spwd结构包含以下信息:
struct spwd {
char *sp_namp; //用户登录名
char *sp_pwdp; //加密密码
long int sp_lstchg; //上次更改密码以来的时间
long int sp_min; // 进过多少天后可以更改
long int sp_max; //要求更改的剩余天数
long int sp_warn; // 到期警告的天数
long int sp_inact; // 账户不活动之前尚余天数
long int sp_expire; //账户到期天数
unsigned long int sp_flag; //保留
那么我们根据:
struct spwd* sp=getspnam("root");//得到spwd结构体
char* p=sp->sp_pwdp;//获取加密密码
加密密码,由3部分组成:
$加密算法ID$加密密钥$密文
获取到它可以为我们密码验证做铺垫,如果我们将我们输入的密码,按照相同的加密算法,密钥进行加密,如果和系统的一样,就证明我们密码输入正确。
当我们输入密码后,系统会将我们的密码进行加密存储,我们学习一种常用的加密函数,即crypt
函数,是一种单向加密技术,不可还原,同时不在C默认库里面,在crypt库里面,运行时需要手动连接。函数原型为:
char* crypt(const char* key,const char* salt);//key是我们需要传入的明文,即自己输入的密码,salt是我们指定的加密算法和密钥。
成功返回加密后的密文,失败返回NULL
salt一般为:
$id$密钥;//id为加密算法类型,密钥不超过8字符
常用的加密算法,即id的取值为:
id | 算法类型 |
---|---|
1 | MD5 |
2a | Blowfish |
5 | SHA-256 |
6 | SHA-512(当前Linux采用的加密算法) |
最后形成的加密信息为:
$id$密钥$明文
Linux系统中我们经常使用的权限有r,w,x,其实除了这3个权限外还支持s,t权限,我们今天主要了解s权限。
s权限:设置使文件在执行阶段具有文件所有者二等权限,相当于临时拥有文件所有者的身份
。如访问shadow中的信息,需要管理员权限,那么我们就可以给它添加s权限。我们采用字符模式设置s权限:
chmod a+s filename;//给这个文件的所有者都添加s权限
在设置s权限前,必须要先设置x权限,否则chmod虽然不报错但无法s权限无法生效
回显功能是显示屏幕上输入的信息,系统默认是存在回显功能的,但是当我们输入密码时,我们不希望别人看到我们的密码,那么就需要取消系统的回显功能。
回显功能的设置需要设置终端的本地模式,那么就需要了解一个结构体,termios
结构体中保存了终端不同模式的成员变量,通过改变成员变量的值就可以改变终端的相关模式。termios结构体包含:
# include
struct termios{
tcflag_t c_iflag;//输入模式
tcflag_t c_oflag;//输出模式
tcflag_t c_cflag;//控制模式
tcflag_t c_lflag;//本地模式
cc_t c_cc[NCCS];//特殊控制字符
}
我们可以调用函数tcgetattr
来初始化和当前终端对应的termios结构:
# include
int tcgetattr(int fd,struct termios*termios_p);//参数fd为终端的文件描述符,再传入temios结构体
这样会把当前终端接口变量的值写入termios_p参数指向的结构。
我们可以对termios结构体中的变量进行修改,修改过后需要再调用tcsetattr
重新配置终端接口:
# include
int tcsetattr(int fd,int actions,const struct termios* termios_p);//文件描述符,修改方式,需要修改的结构体
常见的修改方式有三种:
变量 | 含义 |
---|---|
TCSANOW | 立刻对值进行修改 |
TCSADRAIN | 等当前的输出完成后再对值进行修改 |
TCSAFLUSH | 等当前输出完成后再对值进行修改,但丢弃还未从read调用返回的当前可用的任何输入 |
一定要注意,程序有责任将终端设置恢复到程序开始运行之前的状态,这一点是非常重要的,所以我们需要先保存termios结构体的值,最后再程序结束时恢复它们。
我们现在对本地模式进行不回显修改:不回显宏为
ECHO:启动输入字符的本地回显功能
~ECHO:取消输入字符的本地回显功能
把termios结构体中的本地模式设置为不回显,然后进行重新配置,获得密码后,再将终端配置恢复,这个很重要,否则以后的输入都是不回显的了。
所以我们可以:
struct termios oldter,newter;//定义两个保存终端信息的两个结构体,oldter用来恢复,newter用来设置
tcgetatrr(0,&oldter);//oldter结构体获取到当前终端的模式信息
newter=oldter;//把信息给newter结构体
newter.c_lflag&=~ECHO;//进行本地模式的修改,修改为不回显
tcsetattr(0,TCSANON,&newter);//进行修改后的配置,立即修改
fgets(password,127,stdin);//获取不回显的信息
tcsetattr(0,TCSANON,&oldter);//恢复系统原来的回显功能
分割函数可以按照一定的标识将字符串切割,函数原型为:
# include
char* strtok(char s[],const char* delim);//s为要分解的字符串,delim为分割字符
返回值:
从s开头开始一个个分割,当s中的字符查找到末尾是,返回NULL;如果找不到分割字符,则返回当前strtok的字符串指针。
首次调用时,s指向要分割的字符串,之后再次调用把s设为NULL,表示接着上次分割的位置继续分割。
如,将字符串“hello$world!"进行分割,分割字符保存在buff数组中:
char* s="hello$world!"
char* p=strtok(s,"$");
cout<
初始化数组,我们可以用memset
,函数原型:
# include
void* memset(void*s,int ch,size_t n);//s为指定数组,ch为初始化数组的数值,n个字节
返回替换好的指向数组的指针
如将buff数组初始化为0:
char buff[4];
memset(buff,0,sizeof(char*)4);
判断一个字符串是否存在于另一个字符串,用strstr
,函数原型为:
string strstr(string s1,string s2);
如果存在返回一个指针,指向s2在s1首次出现的位置。
如:s1=”hello",s2=“el”,判断s2是否是s1的子串:
char *s1=”hello”;
char *s2=”el”;
printf(“%sn\n.”strstr(s1, s2);
输出ello
要想输出带颜色的信息,printf函数需要遵循一定的格式:
printf("\033[字背景颜色;字体颜色m字符串\033[0m");
函数说明:
\033 表示后面的字符是转义字符,通知终端切换到转义模式,进行特殊处理;
[ 标识命令序列的开始
m 制定将要执行的动作
33[0m ANSI控制码,对屏幕进行控制,此处表示为关闭所有属性,只显示颜色
常见的字体背景颜色和字体颜色如下:
颜色 | 字背景颜色数值(40~49) | 字颜色数值(30~39) |
---|---|---|
黑色 | 40 | 30 |
红色 | 41 | 31 |
绿色 | 42 | 32 |
黄色 | 43 | 33 |
蓝色 | 44 | 34 |
紫色 | 45 | 35 |
深绿 | 46 | 36 |
白色 | 47 | 37 |
输出红色的字体,白色背景的字符hello:
printf("\033[47;31mhello\033[0m");
可以对ANSI控制码进行改变来达到不同的效果 ,常见的ANSI控制码如下:
控制码 | 含义 |
---|---|
33[0m | 关闭所有属性 |
33[1m | 设置高亮度 |
33[4m | 下划线 |
33[5m | 闪烁 |
33[7m | 反显 |
33[8m | 消隐 |
33[30m – 33[37m | 设置前景色 |
33[40m – 33[47m | 设置背景色 |
33[nA | 光标上移n行 |
33[nB | 光标下移n行 |
33[nC | 光标右移n行 |
33[nD | 光标左移n行 |
33[y;xH | 设置光标位置 |
33[2J | 清屏 |
33[K | 清除从光标到行尾的内容 |
33[s | 保存光标位置 |
33[u | 恢复光标位置 |
33[?251 | 隐藏光标 |
33[?25h | 显示光标 |
加油哦!。