6
11
2012
11

rpysh——Windows Python 命令行也要 readline!

rpysh 是为习惯 Linux 的 Pythoners 在不得不处理 Windows 上的事务时写的远程 shell。

源起

前些天,我尝试了使用 Python 控制 Word。但我对 Windows 下的交互式 Python shell 很不满意。

首先,我尝试的是 cmd.exe 那个黑窗口。太难用了!只有最基本的行编辑、在不知不觉中历史记录被窜改、复制粘贴极其麻烦。补全当然也是没有的。

于是,尝试 IDLE。这家伙我选了「IDLE Classic Unix」,但是能工作的键并不多。比如我刚刚尝试的Ctrl-u就不管用。而Ctrl-p竟然是把光标向上移动,回车才会把那行的内容取到输入命令的那行。这样一来,想再次执行最后一条语句,需要视上条命令输出的行数按几下Ctrl-p。另外,鼠标在窗口内点击后光标会被移开。这样,我使用鼠标从其它窗口切回来时,还得再手动定位光标,极其不爽。至于补全么,太智能了,所以在我输入时不时会出现这种情况:

乱七八糟的补全

还有一个问题:我查资料、做笔记、写代码都在 Linux 上,虽然Ctrl-CCtrl-V在物理机和虚拟机间能够无缝操作,但比起选中+中键粘贴的 X 主选区还是麻烦多了!

没办法,我只好重拾很久以前的想法——写个程序,在 Linux 上操作,在 Windows 上执行!

——等等!这和 ssh 差不多吗?或者 telnet?

——不不,Cygwin 的 ssh 跑不了 Windows 控制台程序,而且,不还是没 readline 支持么?

实现

毫无疑问是网络通信了。距离上一次不成功的尝试已经过去很久了,我不仅更加了解了code模块的能力,也知道 Python 命令行补全是怎么回事了。也就是说,Windows 版的 Python 是有补全的接口的,只是没有 readline 的等价物来调用。跑在 Windows 上的服务端要完成以下操作:

  1. 重写相关方法,把用户数据由标准输入改到从客户端读取
  2. 标准输出重定向到网络 socket
  3. 收到客户端的补全请求后,使用rlcompleter模块获取补全结果,再回送给客户端

对于第一点,实际上取代code.InteractiveConsole实例的raw_input方法就行。它和内建的input()函数具有相同的输入和输出形式,也就是会接收命令提示符。将这个直接发给客户端好了。

第二点很简单,直接socket.makefile然后把sys.stdout指过去。

第三点,为了简单起见,我另开了个线程和 socket,专门用于补全。需要传递的参数和返回值全部 pickle 了扔给对方就是了。

写完这些我才发现,其实我的raw_input方法和补全函数具有相似的执行逻辑:发送参数到网络,再从网络获取执行结果——也就是远程过程调用呵。

使用方法

rpyshd.py可选一个参数作为端口号,为方便起见,提供默认值8980。也是为了方便双击执行起见,我添加了.py后缀。

rpyshc相当于telnet命令了,直接接主机地址和端口号两个参数即可。

缺陷

  • 从标准输入读数据时在服务端
  • 偶尔提示符出现不及时
  • 虽然我实现了Ctrl-C,但是实际上没什么用,因为收到消息时之前的操作肯定已经执行完了
Category: python | Tags: linux python readline windows
6
6
2012
12

编程获取本机IPv4及IPv6地址

首先,我要通过编程直接获取,而不是去读诸如ifconfig等命令的输出。

其实是只想获取IPv6地址的,不过我猜想它们差不多,也确实看到不少相关搜索结果,于是顺带着看了。

首先,使用gethostbyname查自己通常是不行的,因为可能得到127.0.0.1,而且我猜,这样不能处理拥有多个IPv4地址的情况。另外一种方式是连上某个主机,然后调用getsockname。这样需要能够直接连上那个主机,好处是如果有多个网络接口,这样可以知道到底走的是哪个接口,调试网络时不错。我最满意的方案在这里,使用ioctl来获取。这个方法可以获取指定网络接口的IPv4地址。至于有哪些网络接口嘛,直接读/proc/net/dev吧。

import fcntl
import socket
import struct
ifname = b'eth0'
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 0x8915 是 SIOCGIFADDR
ip = socket.inet_ntoa(fcntl.ioctl(s.fileno(), 0x8915, struct.pack('256s', ifname[:15]))[20:24])
print(ip)

然而,这样只能获取IPv4地址。创建个AF_INET6的 socket 传过去会报错「Inappropriate ioctl for device」。那怎么办呢?Google 没找到,我去搜了下内核源码。inet_ioctl里有对SIOCGIFADDR的处理。但是,inet6_ioctl里却没有了。

于是,我只好去下载ifconfig所属的 net-tools 的源码,找到相关代码:

#if HAVE_AFINET6
    /* FIXME: should be integrated into interface.c.   */

    if ((f = fopen(_PATH_PROCNET_IFINET6, "r")) != NULL) {
    while (fscanf(f, "%4s%4s%4s%4s%4s%4s%4s%4s %08x %02x %02x %02x %20s\n",
              addr6p[0], addr6p[1], addr6p[2], addr6p[3],
              addr6p[4], addr6p[5], addr6p[6], addr6p[7],
          &if_idx, &plen, &scope, &dad_status, devname) != EOF) {
        if (!strcmp(devname, ptr->name)) {
        sprintf(addr6, "%s:%s:%s:%s:%s:%s:%s:%s",
            addr6p[0], addr6p[1], addr6p[2], addr6p[3],
            addr6p[4], addr6p[5], addr6p[6], addr6p[7]);

这里就是ifconfig输出IPv6部分的代码了。可以看到它打开了一个奇怪的文件。跟过去,发现是

#define _PATH_PROCNET_IFINET6       "/proc/net/if_inet6"

囧,这个文件我早就发现过了的。看来和IPv4的情况不同,IPv6地址只能通过/proc里的文件获取了。而且输出成人可读格式不容易(ifconfig是自己实现的)。

PS: 我还发现了件好玩的事,在 Linux 源码的include/linux/sockios.h中,SIOCGIFINDEX中的字母 C 写漏了。通过git blame我发现,这个拼写错误在至少七年前 Linux 内核代码迁移到 git 前就修正了。Linus Torvalds 说之前的代码导入到 git 后有 3.2GB。我不得不承认这是个无比正确的决定,因为现在的.git已经有600多兆了,git 不支持断点续传,clone 下来已经很不容易了。

另外,我还联想到了 Unix 系统调用中的creat,以及 HTTP 协议中的referer :D

#define SIOCGIFINDEX    0x8933      /* name -> if_index mapping */
#define SIOGIFINDEX SIOCGIFINDEX    /* misprint compatibility :-)   */
Category: Linux | Tags: C代码 linux python 网络
4
29
2012
53

放弃 gnome-terminal,转到 Xfce 终端

前日进行了系统升级,结果分外悲剧。在 Awesome 下,Empathy 和 gnome-terminal 都不能正常使用了。当然,Awesome 并没有升级,它很稳定。关于 Empathy 的事下篇再写,本篇只吐槽 gnome-terminal。

早些时候,我就发现我经常不能在 gnome-terminal 中成功打开输入法。昨天将 gnome-terminal 从 3.2.1-1 版本升级到 3.4.1.1-1 (以及相伴的其它组件升级)后,我发现我已经很难遇到输入法「恰巧」能够打开的时候了。于是给 GNOME 他们报了 bug。而昨天和今天早些时候,只好使用 vimim 或者复制的方式来在终端里输入中文。今天,收到 gnome-terminal 开发者 Christian Persch 的回复:

Please test if this is reproducible with either gnome-shell or metacity (latest versions from gnome 3.4) in click-to-focus mode. Anything else is entirely unsupported.

他说,如果不能在 GNOME 的组件中重现,那么他们不会修复。操你妹啊,你丫又没有平铺式WM!这不就相当于我说你们的某家电在我家里无法使用,经销商却说你们要是不能在商场里重现问题那不关他们的事么!

于是决心放弃一切有问题的 GNOME 组件,换终端了。我不想用 guake 那种特别的终端,因为一个普通的正常终端在 Awesome 下已经被我调教得很听话了。先尝试的是 lxterminal。忽略掉不完整的中文翻译,开始迁移自己的配置。终端光标不能更改为竖线就算了,竟然没办法通过命令行参数的方式指定窗口的任何可以用作区分的参数。原来 gnome-terminal 我是指定 class 的,这样我可以设置一个流动终端——按一个组合键把它叫过来,用完后再按个键让它离开。但这样做必须能够匹配到那个窗口;我还有些放 mosh 会话的终端,我可不希望它们也跟过来。

又尝试了 Xfce 的终端。这个长得和 gnome-terminal 已经比较像了。看看命令行选项,能设置 role。这个足够用了。配置下字体、颜色什么的,再去改改 Awesome 的配置就可以用了。途中还修整了下run_or_raise函数的匹配逻辑。

PS: GTK 3 更新到 3.4 后,UI 发生了很大的变化,如:

  • 数字输入框的加减号变大了,更占地方,点击时也需要移动更远的距离了;
  • 滚动条变窄变丑了,点中它更难了;
  • gnome-terminal 的颜色设置比 Windows 的取色器还难用了;
  • 好端端的复选框不用,非得弄成滑块,这个是要让用户练习鼠标的拖动操作么?
Category: Linux | Tags: GNOME gtk 终端 linux awesome
4
18
2012
5

Haskell 实战:惰性地读取子进程输出

突然想给 locate 命令写个 wrapper,把输出中的家目录和一些因加密而引入的软链接显示为~。自然,这需要读取 locate 命令的输出。在 process 这个库中看到了readProcess函数,似乎是自己想要的(完整代码):

readLocate :: [String] -> IO String
readLocate args = getArgs >>= \cmd ->
  let args' = args ++ cmd
  in readProcess "locate" args' ""

结果却发现,原本 locate 命令是边查找边输出的,现在变成了先静默,然后一下子全部吐出来。没有按 Haskell 惯常的「懒惰」脾气来。这样一来,当我发现输出项目太多想按Ctrl-C中断时已经晚了。

Google 了一下,找到这个

I guess people who want laziness can implement it themselves directly, taking care to get whatever laziness it is that they want.

好吧。我先下回 process 库的源码看看readProcess为什么不是惰性的:

readProcess 
    :: FilePath                 -- ^ command to run
    -> [String]                 -- ^ any arguments
    -> String                   -- ^ standard input
    -> IO String                -- ^ stdout
readProcess cmd args input = do
    (Just inh, Just outh, _, pid) <-
        createProcess (proc cmd args){ std_in  = CreatePipe,
                                       std_out = CreatePipe,
                                       std_err = Inherit }

    -- fork off a thread to start consuming the output
    output  <- hGetContents outh
    outMVar <- newEmptyMVar
    _ <- forkIO $ C.evaluate (length output) >> putMVar outMVar ()

    -- now write and flush any input
    when (not (null input)) $ do hPutStr inh input; hFlush inh
    hClose inh -- done with stdin

    -- wait on the output
    takeMVar outMVar
    hClose outh

    -- wait on the process
    ex <- waitForProcess pid

    case ex of
     ExitSuccess   -> return output
     ExitFailure r -> 
      ioError (mkIOError OtherError ("readProcess: " ++ cmd ++ 
                                     ' ':unwords (map show args) ++ 
                                     " (exit " ++ show r ++ ")")
                                 Nothing Nothing)

原来是另开了一 IO 线程读输出,然后等待进程结束后关闭管道。这解释为什么它不是惰性的——它得进程善后处理。

那好吧,改用createProcess好了:

doLocate :: IO (String, ProcessHandle)
doLocate = do
  argv0 <- getProgName
  let args = case argv0 of
                  "lre" -> ["-b", "--regex"]
                  _ -> []
  args' <- getArgs
  let args'' = args ++ args'
  (_, Just out, _, p) <- createProcess (proc "locate" args''){ std_in = Inherit,
                                                               std_out = CreatePipe,
                                                               std_err = Inherit }
  hSetBuffering out LineBuffering
  (,) <$> hGetContents out <*> return p

改进后的程序,不会等待进程结束,而是返回输出和进程句柄。进程句柄用来等待子进程结束,同时获取退出状态。至于那个管道就不关闭了,留给操作系统解决好了。

main = do
  (out, p) <- doLocate
  putStr $ transform out
  waitForProcess p >>= exitWith

改进版的完整程序在此

Category: Haskell | Tags: Haskell linux
4
2
2012
0

Linux Multitask Mode

今天,哦不,已经是昨天了,看到 Google 的「Chrome Multitask Mode」。觉得挺好玩,于是亲自试了试——当然不是在 Chrome 里,而是在 Linux 里。准确地说,是在 X Window 里。我给系统弄出一对鼠标指针来了 ;-)

先看看我有哪些输入设备:

xinput --list
⎡ Virtual core pointer                          id=2    [master pointer  (3)]
⎜   ↳ Virtual core XTEST pointer                id=4    [slave  pointer  (2)]
⎜   ↳ SynPS/2 Synaptics TouchPad                id=12   [slave  pointer  (2)]
⎜   ↳ TPPS/2 IBM TrackPoint                     id=13   [slave  pointer  (2)]
⎜   ↳ USB Optical Mouse                         id=9    [slave  pointer  (2)]
⎣ Virtual core keyboard                         id=3    [master keyboard (2)]
    ↳ Virtual core XTEST keyboard               id=5    [slave  keyboard (3)]
    ↳ Power Button                              id=6    [slave  keyboard (3)]
    ↳ Video Bus                                 id=7    [slave  keyboard (3)]
    ↳ Power Button                              id=8    [slave  keyboard (3)]
    ↳ Integrated Camera                         id=10   [slave  keyboard (3)]
    ↳ AT Translated Set 2 keyboard              id=11   [slave  keyboard (3)]
    ↳ ThinkPad Extra Buttons                    id=14   [slave  keyboard (3)]

一个鼠标指针不够,我还要一个:

xinput --create-master xxx

现在屏幕上就多了个鼠标指针了。但是它还不能动,因为没有对应的设备。那好,我的 USB 鼠标,你去控制另外个指针吧:

xinput --reattach 9 "xxx pointer"

Voilà!两个鼠标指针都能用了哦~

不过,这样做还是有些缺点的。比如,它会让火狐等程序迷惑不解,还是当成只有一个鼠标。在火狐中,鼠标的形状总是一样的,并且,不能同时触发两个不同的 onhover 事件。再比如,没有相关联的键盘,所以这个「新的」鼠标指针没办法配合键盘工作的,比如按住某键再点击什么的就不行。

好了,玩完了,还是回到一个鼠标指针的模式吧:

xinput --reattach 9 "Virtual core pointer"
xinput --remove-master "xxx pointer"

参考文章

Category: Linux | Tags: linux X Window X window
3
18
2012
4

使用 gnokii 读取 3G 网卡的短信

使用 gnokii 读取 3G 网卡短信的方法ArchWiki上有写。安装 gnokii 后复制配置文件并将自己添加到uucp用户组中:

cp /etc/gnokiirc ~/.config/gnokii/config
sudo gpasswd -a `whoami` uucp

然后修改下配置文件,主要是port = /dev/ttyUSB0model = AT这两处。用户组的修改要下次登录时才生效,或者使用newgrp命令来登录到uucp组。据说此命令在某些 shell 里是内建命令,不过在 bash 和 zsh 里没有,只能调用外部命令,所以会开启一个新的 shell。

newgrp uucp

现在就可以读短信了。接收新信息并存储,可使用命令

gnokii --smsreader

完事之后按Ctrl-C中断。要把短信读出来,gnokii 可以把短信存储为 mbox 格式。这是一种邮件格式,使用 mutt 即可阅读。-f后边的sms即是要保存的文件名。注意,gnokii 仍会将消息输出到终端。ArchWiki 上说的是使用 xgnokii GUI 程序来读取,但是我没有找到这个程序(只有 manpage)。

gnokii --getsms SM 0 end -f sms

对于中文短信,这样会导致乱码。所以我写了个 Python 脚本来处理。三件事:一是将内容编码标识为 UTF-8,二是把按字节截断的邮件主题(短信正文的前若干个字节)最后几个无效的编码替换掉,三是将邮件主题按标准进行编码。这些事 Python 处理起来挺容易的 ;-)

#!/usr/bin/env python3
# vim:fileencoding=utf-8

import os
import sys
import email.header

for i in os.fdopen(sys.stdin.fileno(), encoding='utf-8', errors='replace'):
  if i.startswith('Subject: '):
    s = i[9:-1]
    print('Content-Type: text/plain; charset=utf-8')
    print('Subject:', email.header.Header(s, 'utf-8').encode())
  else:
    sys.stdout.write(i)
Category: Linux | Tags: LInux Python 网络 mutt
3
3
2012
6

GM 脚本:github 默认下载格式改为 gzip

不知道为什么,新版 github 在项目首页上只提供 zip 归档格式的下载,要下载 gzip 包还需要点击 download 到新页面去。可是我就是不喜欢 zip 格式嘛——

// ==UserScript==
// @name           github 下载默认 gzip 格式
// @namespace      http://lilydjwg.is-programmer.com/ 
// @description    把默认的 zip 下载按键改成 gzip 的
// @include        https://github.com/*
// ==/UserScript==
 
var dl = document.querySelector('.btn-download');
if(dl){
  dl.title = dl.title.replace('zip', 'gzip')
  dl.href = dl.href.replace('/zipball/', '/tarball/');
  var icon = dl.querySelector('.icon');
  if(icon){
    icon.nextSibling.textContent = 'GZIP';
  }
}
Category: 火狐 | Tags: GreaseMonkey 火狐 linux github
2
28
2012
8

拯救分区表

今天,因为弄错了磁盘顺序,不小心把移动硬盘的前 40M 左右的数据覆盖掉了。所幸发现的时候移动硬盘还接在电脑上,除了第一个分区外,后边的还可以正常挂载和读取,这说明内核还记得分区表。但是fdisk已经读不到 MBR 了。

怎么办呢?我不想把几百 G 的数据拷一遍,而且理论上肯定是能够把分区表给完整的找回来的——内核不是还记得么?上网只搜到/proc/partitions这个文件,其中的内容如下:

major minor  #blocks  name

   8        0  312571224 sda
   8        1   52436128 sda1
   8        2   52428800 sda2
   8        3          1 sda3
   8        5     512000 sda5
   8        6   41943040 sda6
   8        7  161281024 sda7
   8        8    3964928 sda8
  11        0    1048575 sr0
   7        0      71680 loop0
   8       16  312571224 sdb
   8       17    1048576 sdb1
   8       18  209715200 sdb2
   8       19   10485760 sdb3
   8       20   91320320 sdb4

只有分区的大小信息,而且单位是。我按移动硬盘的大小推算了下,这里的块大小是1KiB(关于块大小,真够混乱的。ls默认的也是 1K,但是dd却是 512B)。

光知道了块大小不行啊。我先试了试著名的 testdisk 工具。它搜索了好久,最终只找到了两个分区,于是被我否决了。又继续找分区的更多信息。/proc下看完了,我又去不怎么了解的/sys下看,发现其下有个block目录,里面正是系统已经识别的块设备!

进到出事故的sdb下,再进入sdb1ls一下,看到sizestart都在呢!cat出来各是一个整数。经过一番猜测和计算,可以确定其单位是512B,也就是一个扇区

好了,可以开始重建分区表了。当然,我可不想手工去算和写那64字节的二进制数据。试了试 Arch 安装时所用的cfdisk。它有基于文本的图形化界面,比较友好。可是新建分区时才发现只能输入以1000进制MB为单位的大小,而我需要分毫不差的按原大小分区。只好退出,试试文本交互的fdisk。在不断地按m键查看帮助的情况下,终于把分区重建好了:

  Device Boot      Start         End      Blocks   Id  System
/dev/sdb1            2048     2099199     1048576   83  Linux
/dev/sdb2         2099200   421529599   209715200   83  Linux
/dev/sdb3       421529600   442501119    10485760   83  Linux
/dev/sdb4       442501120   625141760    91320320+   7  HPFS/NTFS/exFAT

前面几个的大小比较整,我是按+1G等这样输入的。这里要注意下的是,G, M, K等单位是1024进制,而GB, MB, KB等单位是1000进制。最后那个给 Windows 留的 NTFS 分区不知道为什么并不是在磁盘的最后一个扇区结束的,我输入的是/sys/block/sdb/sdb4/size里写的大小。

p查看并确认分区表正确后,按w写入。然后使用partprobe命令通知内核更新分区表信息。

这步做完后,后边的三个分区就安全了。第一个分区是一些启动文件,我已经打算重新弄一遍了。实际上fsck.ext2跑完后也只是在lost+found里出现了一堆垃圾文件。mkfs.ext2重新格式化,却在安装 grub2 时遇到了问题。大致的错误信息是这样的:

/dev/sdb appears to contain a iso9660 filesystem which isn't known to reserve space for DOS-style boot.  Installing GRUB there could result in FILESYSTEM DESTRUCTION if valuable data is overwritten by grub-setup (--skip-fs-probe disables this check, use at your own risk)

中文消息是:

/dev/sda 中似乎包含一个不为 DOS 引导保留空间的 iso9660 文件系统。在此处安装 GRUB 可能导致 grub-setup 覆盖重要数据从而损坏文件系统(--skip-fs-probe 参数可以禁用这个检查,使用该选项风险自负)

这个「iso9660」文件系统就是我误dd过去的。使用grub-setup并加上--skip-fs-probe参数后依旧出错:

warn: Attempting to install GRUB to a disk with multiple partition labels or both partition label and filesystem.
error: embedding is not possible, but this is required for cross-disk install.

中文消息是:

警告:正在试图将 GRUB 安装至有多个分区标签的磁盘,或同时有分区标签和文件系统的磁盘。这样的操作尚未被支持。
错误:无法嵌入,但在跨盘安装时是必须的

加上--force参数也没有用。我尝试消除前446字节的数据,亦没有用。后来想起在使用fdisk分区时,第一分区的起始扇区必须大于等于2048。难道是这些扇区中的内容影响了 grub2 的安装?head -c 1024 > a然后用 bviplus 查看,发现果然如此,都看到那个已经不完整的 iso9660 文件系统的卷标了。果然给它 dd 掉:

sudo dd if=/dev/zero of=/dev/sdb seek=1 count=2047 bs=1b

再次尝试安装,一切顺利!

Category: Linux | Tags: linux 失误 数据恢复
2
23
2012
8

迁移到64位 Arch Linux

曾经因为不了解,所以一开始选择了32位系统;后来内存大了,ArchLinux 的内核没有编译PAE支持,只能用 3GiB 多的内存,不爽。朋友又给了这篇教程,于是略作准备就开始动手了。

可是,但我真正动手时,那篇文章已经被另一篇英文教程取代了。当时忘记了 MediaWiki 会记录所有的编辑历史,所以只好将就着看了。结果,重启时 udev 没跑起来,说找不到 librt.so。我当然不会就此罢休,花费了一些时间,不仅拯救了系统,成功迁移到了 64 位,而且确定了 ArchWiki 上那篇教程的错误。

先说这个错误吧。现在已经修正了。方法 2: 从正在运行的系统一节,在安装 busybox 的时候,现在是一个红色的警告框,暗示着这里曾经发生的悲剧——

警告: 不要现在安装 lib32-glibc 软件包。在执行命令 ldconfig 后,当你安装 linux(内核)时,生成的镜像文件中,librt.so 等库文件会在 /usr/lib32 目录下,启动的时候二进制文件不会在此搜索库文件,导致启动失败。

当悲剧发生后,我有些紧张地拿出移动硬盘,先是进入移动硬盘上的 Arch,胡乱安装了几个 64 位软件包并重新生成 initramfs,结果连 init 也执行不了了。无奈我又启动64位内核的 PartedMagic chroot 进去查看。但是,chroot 失败了:

sh: cannot open shared object file

后来我才知道,因为我安装lib32-glibc/lib/ld-linux.so.2发生冲突,我覆盖了;后来卸载 32 位的 glibc 时,它被删除了。于是,动态链接的 32 位程序没有动态库加载器了。但其实还是有办法的,因为被误删的只是个软链接。

/usr/lib32/ld-linux.so.2 /bin/ls

老猫的提示下,手动指定ld-linux.so.2运行成功。我尝试把它链接过去,这个 32 位与 64 位库混合的系统开始有些可用了。接下来按 Wiki 里的指示操作就可以了。不过,我没有重装全部的库,而是只装了标明 i686 架构的库。这就是我之前那个用 Haskell 写的脚本的目的。不过还是出了点小问题——虽然我为防止程序运行不了而在开始之前把软件包列表生成了一份保存起来,但我忘了我的 HOME 是加密过的。PartedMagic 里没有 eCryptfs 工具,而 Arch 里的那个又因为库的原因运行不了,囧死了。。。幸运的是,我在迁移之前把 Dropbox 弄好同步了一遍,这个软件包列表也同步了。通过 Dropbox 网页界面下回来就好了。

弄好重启之后,整个事情还没完——我从 AUR 里编译安装的那些包还没重装呢。边重装边研究新的 64 位系统,发现 gcc 还是可以编译出 32 位程序的,只是要装gcc-multilib而已。现在可好了,既可以运行 64 位的程序,又可以运行 32 位的。库依赖少的 C 程序编译个 32 位的出来也没问题。

对了,最后说一句,vnstat 这厮的数据库格式竟然是平台相关的!换成 64 位后它就不断报错,直到我删除了以前的数据库。

Category: Linux | Tags: arch linux
1
28
2012
5

使用 eCryptfs 加密主目录

本文根据回忆记述在 Arch Linux 上为某一新用户建立使用 eCryptfs 加密的 $HOME 目录并使之在登录时自动解密挂载的过程。大量参考了 Unknown and partly hidden 的 eCryptfs and $HOME 一文。

依赖的软件包:ecryptfs-utils。

加密目录

# mkdir -p /home/.ecryptfs/user/private
# chmod 755 /home/.ecryptfs
# chmod -R 700 /home/.ecryptfs/user
# chown -R user:user /home/.ecryptfs/user
# ln -s /home/.ecryptfs/user/private /home/user/.private
# chmod 700 /home/user

注意:最后一步原文使用的是500权限,这里改成了700

第一次挂载加密目录:

# mount -t ecryptfs /home/user/.private /home/user

eCryptfs 会询问一些加密的选项,其中 Cypher(加密方法)和 Key byte 可自行选择:

Key type: passphrase
Passphrase: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Cypher: twofish
Key byte: 32
Plaintext passtrough: yes
Filename encryption: yes
Add signature to cache: yes

一定要记住密码,虽然可能并不怎么会用到。

mount命令的输出中找到这次挂载使用的参数,经过一些变更,把类似于以下的设置添加到/etc/fstab中:

/home/user/.private /home/user ecryptfs rw,user,noauto,exec,ecryptfs_sig=XYZ,ecryptfs_cipher=twofish,ecryptfs_key_bytes=32,ecryptfs_passthrough,ecryptfs_fnek_sig=XYZ,ecryptfs_unlink_sigs 0 0

注意:在登录挂载时,noexecnosuidnodev将会是默认选项。这里加上exec选项来覆盖掉noexec,这样加密的 $HOME 中才支持执行可执行文件。

挂载时生成了/root/.ecryptfs目录。我们先在里边保存些文件:

# touch /root/.ecryptfs/auto-mount
# ecryptfs-wrap-passphrase /root/.ecryptfs/wrapped-passphrase
Passphrase to wrap: [输入加密口令]
Wrapping passphrase: [输入用户的登录口令]

现在,使用用户的登录口令可以从文件/root/.ecryptfs/wrapped-passphrase中得到 eCryptfs 的加密口令。即使加密口令很强,如果登录口令弱的话,文件信息还是会泄漏的。所以,得选个强的登录口令,不然就不要玩登录时自动挂载加密 $HOME 了。

或者,你也可以玩点有趣的,把这个wrapped-passphrase文件放在 U 盘里,只留下一个指向 U 盘里的此文件的软链接。然后配置好 U 盘自动挂载,就做成了个简单的「U 盾」!

好了,现在卸载 $HOME:

# umount /home/user

自动挂载

先把 eCryptfs 的那个在/root下的目录弄回来:

# mv /root/.ecryptfs /home/.ecryptfs/user
# chown -R user:user /home/.ecryptfs/user/.ecryptfs
# ln -s /home/.ecryptfs/user/.ecryptfs /home/user/.ecryptfs

接下来,创建一个挂载用的脚本,暂时叫它/home/profile.sh吧。它将被写到用户登录时的自动执行脚本中,如~/.profile,或者~/.zprofile,如果你用 Zsh 的话。

if [ -r "$HOME/.ecryptfs/auto-mount" ]; then
  grep -qs "$HOME ecryptfs" /proc/mounts
  if [ $? -ne 0 ]; then
    mv $HOME/.Xauthority /tmp 2>/dev/null
    mount -i "$HOME"
    cd "$HOME"
    mv /tmp/.Xauthority $HOME 2>/dev/null
    (
      systemctl --user daemon-reload
      systemctl --user default
    ) &
  fi
fi

注意到这里加入了对~/.Xauthority文件的处理,不然从图形界面登录时,执行挂载命令后,会因授权文件不见了而失败。之前把 $HOME 的权限设置成700也是为了这个。单纯地允许写~/.Xauthority不行,因为 xauth 需要创建临时文件以防止此文件同时被多个进程修改。

现在,我们需要在用户登录时自动 unwrap 之前创建的那个wrapped-passphrase文件。在/etc/pam.d/login中添加几行(注意顺序):

#%PAM-1.0
#...
auth                required        pam_unix.so nullok
auth                required        pam_ecryptfs.so unwrap
#...
password            required        pam_ecryptfs.so
#password           required        pam_unix.so sha512 shadow use_authtok
#...

好了,我们先手动试试:

# su user
$ ecryptfs-insert-wrapped-passphrase-into-keyring /home/user/.ecryptfs/wrapped-passphrase
Passphrase: [输入用户密码]
$ mount -i /home/user

如果正确挂载的话,接下来就可以开始建设你的新 $HOME 了,比如把你以前的各种文件复制过去,等等。注意不要在加密的目录内进行 BT 下载哦。你可以建立个/home/.ecryptfs/user/public目录然后软链接到 $HOME 内来使用。

我同时还修改了/etc/pam.d/slim,似乎这样才能在使用 slim 登录时也有效。

呃,还没有结束呢。得把之前的/home/profile.sh文件弄进来。这里演示时只是创建了一个新的.profile文件。如果你已经有了此文件的话,一定不要将其加密,而要将其与此挂载脚本合并。它只能不加密,否则挂载后会出现两个.profile(一个加密了的,一个未加密、passthrough 来的),从而导致一些问题。

# umount /home/user
# chmod 600 /home/profile.sh
# chown user:user /home/profile.sh
# mv /home/profile.sh /home/.ecryptfs/user/private/.profile
# ln -s /home/.ecryptfs/user/private/.profile /home/user/.profile

好了,到此一切结束。

Category: Linux | Tags: linux 安全

| Theme: Aeros 2.0 by TheBuckmaker.com