鉴于QQ 4 Linux实在太不好用,有些不得不使用QQ的Linux用户使用Wine来运行QQ,我经过努力也成功了,但依旧不用,因为在去年的3Q大战中我们看到腾讯实在难以信任。想想我的ssh密钥、GPG密钥、getmail和msmtp的配置文件等等非常重要的文件QQ皆可轻易扫描并上传……
想过使用另外的低权限用户来wine QQ。然而即使这样,我自己的众多文件QQ依然可以看到,心中还是不安,而且这样作为QQ的重要功能之一——互传文件——将很不方便。于是终于有一天,我开始折腾chroot了。为了不需要总是sudo,我用C写的,可以使用Linux的suid特性,同时也练习下C编程。在man相关系统调用的时候,我还发现了unshare这个调用,竟然可以把挂载的文件系统设置成只在新的挂载命名空间(mount namespace)中可见。这样就可以把为了chroot而mount --bind的项目全部隐藏起来,免得mount输出一大堆不想看到的东西。至于umount嘛,不管了,反正只是mount --bind的。
/* ===================================================================== *
* chrooted wine QQ
* ===================================================================== */
#define _GNU_SOURCE
#include<unistd.h>
#include<sys/stat.h>
#include<sys/types.h>
#include<sys/mount.h>
#include<linux/limits.h>
#include<sched.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
#define ROOT "/tmp/.rwine"
#define PROGRAM "你的QQ的目录"
/* 文件共享目录;若不存在会自动创建 */
#define SHAREDIR "哪个目录作为共享?"
#define BIN "Bin/QQ.exe"
#define ROOTD(dir) ROOT dir
#define MKDIR(dir) mkdir(ROOTD(dir), 0777)
#define MOUNTBIND(dir) mountbind(dir, ROOTD(dir))
int mountbind(const char *source, const char *target);
/* --------------------------------------------------------------------- */
int main(int argc, char **argv){
int ret;
ret = unshare(CLONE_NEWNS);
if(ret){
perror("unshare");
exit(1);
}
seteuid(getuid());
ret = mkdir(ROOT, 0777);
seteuid(0);
if(ret && errno != EEXIST){
perror("mkdir new root dir");
exit(1);
}
seteuid(getuid());
mkdir(SHAREDIR, 0700);
seteuid(0);
setgid(0);
MKDIR("/lib");
MKDIR("/usr");
MKDIR("/usr/share");
MKDIR("/usr/bin");
MKDIR("/usr/lib");
MKDIR("/etc");
MKDIR("/etc/fonts");
MKDIR("/program");
MKDIR("/dev");
MKDIR("/tmp");
MKDIR("/tmp/share");
chmod(ROOTD("/tmp"), 01777);
ret = MOUNTBIND("/lib")
|| MOUNTBIND("/usr/share")
|| MOUNTBIND("/usr/bin")
|| MOUNTBIND("/usr/lib")
|| MOUNTBIND("/etc/fonts")
|| MOUNTBIND("/dev")
|| mountbind(PROGRAM, ROOTD("/program"))
|| mountbind(SHAREDIR, ROOTD("/tmp/share"));
if(ret){
perror("mount --bind");
exit(1);
}
char path[PATH_MAX];
char path2[PATH_MAX];
char *p;
strcpy(path, getenv("HOME"));
strcat(path, "/.wine");
strcpy(path2, ROOT);
p = strtok(path, "/");
seteuid(getuid());
while(p){
strcat(path2, "/");
strcat(path2, p);
mkdir(path2, 0777);
p = strtok(NULL, "/");
}
seteuid(0);
/* path changed by strtok */
strcpy(path, getenv("HOME"));
strcat(path, "/.wine");
strcpy(path2, ROOT);
strcat(path2, path);
ret = mountbind(path, path2);
if(ret){
perror("mount --bind user's wine dir");
exit(1);
}
strcpy(path, getenv("HOME"));
strcat(path, "/.fonts");
if(access(path, X_OK) == 0){
strcpy(path2, ROOT);
strcat(path2, path);
seteuid(getuid());
mkdir(path2, 0777);
seteuid(0);
ret = mountbind(path, path2);
if(ret){
perror("mount --bind user's font dir");
exit(1);
}
}
strcpy(path, getenv("HOME"));
strcat(path, "/.fontconfig");
if(access(path, X_OK) == 0){
strcpy(path2, ROOT);
strcat(path2, path);
seteuid(getuid());
mkdir(path2, 0777);
seteuid(0);
ret = mountbind(path, path2);
if(ret){
perror("mount --bind user's fontconfig dir");
exit(1);
}
}
ret = chroot(ROOT);
if(ret){
perror("chroot");
exit(1);
}
chdir("/tmp");
ret = setuid(getuid());
if(ret){
perror("setuid");
exit(1);
}
execl("/usr/bin/wine", "/usr/bin/wine", "/program/"BIN, NULL);
/* execl("/usr/bin/wine", "/usr/bin/wine", "notepad", NULL); */
perror("execl");
return 1;
}
int mountbind(const char *source, const char *target){
return mount(source, target, NULL, MS_BIND | MS_NOSUID, NULL);
}
/* ===================================================================== *
* vim modeline *
* vim:se fdm=expr foldexpr=getline(v\:lnum)=~'^\\S.*{'?'>1'\:1: *
* ===================================================================== */
程序名为rwine。这里的“r”可不是rsync或者rcp中的“r”,而是“rbash”、“rzsh”、“rvim”中的“r”的意思。在代码的最开头有几个宏是用来配置路径的。suid程序嘛,像这种东西还是硬编码进去好了。因为只是自己使用,所以没有做太多的错误检查之类的,健壮性应该不怎么样。对于这个程序我只要没有安全漏洞就OK了。
另外说个小插曲。在调试这段代码的时候,我发现用户的路径总是挂载得不对,检查了好久,才注意到strtok的声明是
char *strtok(char *str, const char *delim);
第一个参数那里没有const
!再仔细看看man文档,果然,它把传进去的参数给改了。