鉴于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文档,果然,它把传进去的参数给改了。