su 命令用来切换用户
su—》表示默认切换到root用户
su uer --》表示切换到user用户
主体框架
通过mybash–fork–》su–fork–》bash
1/切换到那个用户
2、输入密码
3、密码匹配
4、fork子进程调用bash,父进程等待子进程结束
实现代码:
int main(int argc, char *argv[])
{
char *user="root";
if(argc>1)
{
user=argv[1];
}
printf("Password:");
fflush(stdout);
char passwd[128]={0};
struct termios old, new;
tcgetattr(0,&old);
new=old;
new.c_lflag&=~ECHO;//设置不回显
new.c_lflag&=ICANON;
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);
//sp->sp_pwdp $id$salt$.....第3个$结束
char *p=sp->sp_pwdp;
char salt[128]={0};
int i=0,count=0;
while(*p!=0)//获取密文
{
salt[i++]=*p;
if(*p=='$')
{
count++;
}
if(count==3)
{
break;
}
p++;
}
char *pwd=crypt(passwd, salt);//加密
assert(pwd!=NULL);
if(strcmp(pwd,sp->sp_pwdp)!=0)
{
printf("passwd error");
exit(0);
}
pid_t pid=fork();
assert(pid!=-1);
if(pid==0)
{
struct passwd *UserInfo=getpwnam(user);
setuid(UserInfo->pw_uid);//切换用户
setenv("HOME",UserInfo->pw_dir,1);
execl(UserInfo->pw_shell,UserInfo->pw_shell,(char *)0);
printf("su exec fail\n");
exit(0);
}
else
{
wait(NULL);
}
}
知识点总结:
1、密码的回显控制
struct termios
{
tcflag_t c_iflag //输入标志,通过终端设备驱动程序控制字符的输入
tcflag_t c_oflag //输出标志,控制驱动程序输出
tcflag_t c_cflag //控制标志,影响RS-232串行线
tcflag_t c_lflag //本地标志,影响驱动程序和用户之间的接口(回显的开关ECHO)
cc_t c_cc[NCCS]
}
我们使用的本地标志,来控制密码的回显,通过new.c_lflag &=~ECHO来控制不回显
设置两个termios变量 old 和new 通过old获得当前的终端控制,在赋值给new,在让new设置为不回显,在通过new来设置终端,输入完密码后再,在将终端设置为old,还原终端。
2、获取密文
struct spwd {
char sp_namp; / user login name */
char sp_pwdp; / encrypted password /
long int sp_lstchg; / last password change /
long int sp_min; / days until change allowed. /
long int sp_max; / days before change required /
long int sp_warn; / days warning for expiration /
long int sp_inact; / days before account inactive /
long int sp_expire; / date when account expires /
unsigned long int sp_flag; / reserved for future use */
}
用来描述用户的密码的结构体
每个字段的含义是:
· sp_namp - 指向以 null 结束的用户名的指针
· sp_pwdp - 指向 null 结束的密码的指针
· sp_lstchg - 最近更改密码的日期(日期计算方法是从1970年1月1日开始的天数)
· sp_min - days before which password may not be changed
· sp_max - days after which password must be changed
· sp_warn - days before password is to expire that user is warned of pending password expiration
· sp_inact - days after password expires that account is considered inactive and disabled
· sp_expire - days since Jan 1, 1970 when account will be disabled
· sp_flag - reserved for future use
通过该结构体的getspnam(const char * username);来获取该结构体变量指针。从而得到该用户的密文,密文由$id$salt$encode组成,在得到crypt函参数中的salt变量$id$salt$,对密码进行加密。
3、判断获得的密文是否正确
strcmp(pwd,sp->sp_pwdp);
4、切换用户
通过struct passwd
{
char *pw_name 用户登录名
uid_t pw_uid UID号
gid_t pw_gid GID 号
char *pw_dir 用户家目录
char *pw_gecos 用户全名
char *pw_shell 用户默认shell
};
来获取用户信息,setuid是用来切换用户的,而setenv是来改变当前的环境变量,参数1,是设置是立即改变的。
5、开辟进程
execl来进入用户的环境。