C语言实现su命令

su命令的功能为切换用户,首先看一下系统su命令的效果:


su命令为:su+用户名,没有输入参数时默认为root用户。由普通用户切换到root用户时,需要输入密码,由root用户切换到普通用户时不需要输入密码,而且密码输入时在屏幕上是不显示的。根据这些特点逐步实现速命令。


1、密码不显示设置

通过设置termios类型的数据结构中的值和使用一小组函数调用,就可以对终端接口进行控制。termios数据结构和相关的函数调用都定义在头文件termios.h中。

可以被调整来影响终端的值按照不同的模式被分成:

输入模式、输出模式、控制模式、本地模式和特殊控制字符。

最小的termios结构的定义如下,结构体成员的名称与上面列出的5种参数类型相对应:

#include<termios.h>
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 <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>
#include <sys/types.h>
#include <pwd.h>
#include <shadow.h>
#include <termios.h>

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);
	}
}





  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值