su命令的功能为切换用户,首先看一下系统su命令的效果:
su命令为:su+用户名,没有输入参数时默认为root用户。由普通用户切换到root用户时,需要输入密码,由root用户切换到普通用户时不需要输入密码,而且密码输入时在屏幕上是不显示的。根据这些特点逐步实现速命令。
1、密码不显示设置
通过设置termios类型的数据结构中的值和使用一小组函数调用,就可以对终端接口进行控制。termios数据结构和相关的函数调用都定义在头文件termios.h中。
可以被调整来影响终端的值按照不同的模式被分成:
输入模式、输出模式、控制模式、本地模式和特殊控制字符。
最小的termios结构的定义如下,结构体成员的名称与上面列出的5种参数类型相对应:
#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结构,把当前的终端接口变量的值写入p指向的结构:
int tcgetattr(int fd,struct termios *p);
函数tcsetattr来重新配置终端接口:int tcsetattr(int fd,int actions,const struct termios *p);
参数actions控制修改方式,共有3种修改方式:
TCSANOW:立刻对值进行修改;
TCSADRAIN:等当前的输出完成后再对值进行修改;
TCSAFLUSH:等当前的输出完成后再对值进行修改,但丢弃还未从read调用返回的当前可用的任何输入。
本地模式控制终端的各种特性,c_lflag成员中最重要的两个宏是ECHO和ICANON。ECHO 启用输入字符的本地回显功能,ICANON 启用标准输入处理。
struct termios old, new;
tcgetattr(0, &old);
new = old;
new.c_lflag &= ~ECHO;//设置成不显示
tcsetattr(0, TCSANOW, &new);
fgets(passwd, 128, stdin);
tcsetattr(0, TCSANOW, &old);//密码输入完成后要立即设回回显模式
passwd[strlen(passwd) - 1] = 0;
printf("\n");
这样我们就解决了密码不显示的问题。
那么,密码输入进去以后首先要判断密码是否正确,这里要用到crypt()函数。其原型为char *crypt(const char*key,const char*salt). key是我们传入的明文,salt是我们指定用来加密的密钥,返回值为加密后的密文。那这里关键是要知道salt,那就要用到getspnam函数。
getspnam()函数可以访问shadow口令,其原型为struct spwd *getspnam(const char*name),其返回值为spwd结构体指针。spwd结构部分如下:
struct spwd{
char *sp_namp;//用户登录名
char *sp_pwdp;//加密口令
}
要注意,这里只有root用户才可以调用该函数,所以要用chmod a+s su命令加上s权限,使得不管谁执行起来都具有所有者权限。
接下来就可以比较输入的密码是否正确,如果不正确直接退出,如果正确又会执行什么操作呢?先看一下系统是怎么做的。
通过上图可以发现,我们实现su命令就是如下过程:
整体代码如下:
#include
#include
#include
#include
#include
#include
#include
#include
#include
int main(int argc, char *argv[])
{
char *user = "root";
if(argv[1] != NULL)
{
user = argv[1];
}
//input password
printf("Passwd: ");
fflush(stdout);
char passwd[128] = {0};
struct termios old, new;
tcgetattr(0, &old);
new = old;
new.c_lflag &= ~ECHO;
tcsetattr(0, TCSANOW, &new);
fgets(passwd, 128, stdin);
tcsetattr(0, TCSANOW, &old);
passwd[strlen(passwd) - 1] = 0;
printf("\n");
struct spwd *sp = getspnam(user);
assert(sp != NULL);
char salt[128] = {0};
int i = 0, count = 0;
for(; sp->sp_pwdp[i] != 0; ++i)
{
salt[i] = sp->sp_pwdp[i];
if(salt[i] == '$')
{
count++;
if(count == 3)
{
break;
}
}
}
char *p = crypt(passwd, salt);
assert(p != NULL);
if(strcmp(p, sp->sp_pwdp) != 0)
{
printf("passwd error\n");
exit(0);
}
pid_t pid = fork();
assert(pid != -1);
if(pid == 0)
{
struct passwd *pw = getpwnam(user);
assert(pw != NULL);
setuid(pw->pw_uid);
setenv("HOME", pw->pw_dir, 1);
execl(pw->pw_shell, pw->pw_shell, (char*)0);
printf("error\n");
exit(0);
}
else
{
wait(NULL);
}
}