Host A Host B
+--------------------+------+ +------+--------------------+
| +------------+ | | | | +------------+ |
| | | | SSH | Internet | SSH | | | |
| | +-----> Port +----------------> Port +-----> | |
| | <-----+ <----------------+ <-----+ | |
| | SSH | | | | 22 | | SSH | |
| | Client | +------+ +------+ | Server | |
| | <-----+ | | <-----+ | |
| | +-----> | | +-----> | |
| +------------+ | X11 | | X11 | +------------+ |
| +------------+ | Port | | Port | +------------+ |
| | X11 Server +-----> | | +-----> X11 Client | |
| | <-----+ 6000 | | 6010 <-----+ | |
| +------------+ | | | | +------------+ |
+--------------------+------+ +------+--------------------+
继续阅读 → 最近几天在FreeBSD上尝试搭建SSH服务器,主要是想利用SSH的端口转发 功能。然而却遇到了一个诡异的问题,耗费了我许多时间。
我希望限制每个用户的登录数,即,同一用户在同一时间只能在一个主机上连接SSH服务器进行端口转发。网上查了老半天,基本上没有什么有用的信息。
据说Linux中可以使用/etc/security/limits.conf
来限制用户的登录数量。也有资料说这个办法只能限制shell的登录数量,对不需要shell的端口转发连接无效。
limits.conf
通过pam_limits
模块产生作用,FreeBSD无此模块,因此我无法尝试。受此启发,我找到了pam_exec
模块,允许我在开启/关闭会话的时候执行任意的脚本。这样我只需要自己编写一个脚本来检测是否存在重复登录即可。
#!/bin/sh
# file: pam_session.sh
# prevent multiple ssh connections from a single user
USER_SESSION= "/var/run/ ${ PAM_USER} .ssh_session"
LOG= "/var/log/ssh_session.log"
echo ` date` " $PAM_USER" " $PAM_RHOST" " $PAM_SM_FUNC" >> $LOG
if [ " $PAM_SM_FUNC" = "pam_sm_open_session" ] ; then
if [ -f $USER_SESSION ] ; then
echo BLOCKED by ` cat $USER_SESSION` >> $LOG
exit 1
fi
echo " $PAM_RHOST" > $USER_SESSION
elif [ " $PAM_SM_FUNC" = "pam_sm_close_session" ] ; then
rm -f $USER_SESSION
fi
保存为/etc/ssh/pam_session.sh
,然后在/etc/pam.d/sshd
中添加一行
session required pam_exec.so /etc/ssh/pam_session.sh
即告成功。诡异的事现在发生了:我在一个主机上登录SSH,然后在另一个主机上也登录SSH,但后者只进行端口转发,不登录shell。结果后者也登录成功了!
我在Windows上用Bitvise的SSH客户端 ,连接并没有中断。
我在Android上用ConnectBot 连接,在不登录shell的情况下,连接也没有中断。
我的脚本工作正常,PAM的Session应该是认证失败的(系统日志中有Permission Denied
的记录)。也就是说,OpenSSH忽略了PAM会话认证失败而继续保持着连接!
沿着这样的思路,我尝试了其他各种方法,试图阻断重复的SSH连接,均告失败。绝望的我去查看了OpenSSH的源代码,想搞清楚作者为什么指定了这样的行为。
看到第1052行 :
sshpam_err = pam_open_session (sshpam_handle, 0 );
if (sshpam_err == PAM_SUCCESS)
sshpam_session_open = 1 ;
else {
sshpam_session_open = 0 ;
/*1052*/ disable_forwarding ();
error ("PAM: pam_open_session(): %s" ,
pam_strerror (sshpam_handle, sshpam_err));
}
瞬间释然,其实OpenSSH在PAM会话认证失败的情况下是会禁用端口转发的。至于连接为什么没有中断,我不知道。
且不提OpenSSH的行为是否合适,仅仅因为连接没有中断就判定端口转发仍然可以进行,显然我陷入了惯性思维的陷阱,为此还浪费了大量的时间。应当引以为戒。
继续阅读 → 今天我写了一个玩具垃圾收集器 ,采用_标记-清除(Mark-Sweep )_ 算法。
Wikipedia 上的一张图片解释了该过程。
我写的垃圾收集器之所以称作玩具,是因为它采用了朴素实现。每次垃圾收集会打断其他例程的运行,进行一次完整的标记-清除周期。其缺点是容易造成周期性的延迟,影响程序运行的平顺度。
继续阅读 → 最近我在VirtualBox 上跑FreeBSD 系统。安装好X窗口系统后,自带的中文字体非常丑陋,所以有必要安装一些新的字体。
根据我以往的经验,文泉驿 提供了一套非常优质的中文字体,被许多Linux的发行版选作了默认的中文字体。在FreeBSD中也有wqy
包可供安装。
文泉驿有一款字体称为微米黑,其基于Google的Droid Sans Fallback,通过部件拼接来构造汉字,从而在保持字体体积较小的情况下包含大量的字符。文泉驿制作微米黑是为了补充Droid Sans Fallback缺失的字符。
今天我查看Wikipedia页面的时候,看到Google发布了一款新的字体:Noto 。其名称有一个来源:浏览网页时,字体中不包含的字符通常会被显示为豆腐块。而Noto的目标是消灭豆腐块(No more to fu)。这个字体从2014年开工,一年不到就已完工,整个字体的体积高达460MB,其中绝大部分(400MB以上)是CJK字体。CJK字体包含了简体、繁体、日文和朝鲜文4种变体,以迎合不同地区的习惯。
于是我卸载了文泉驿,改而安装Noto字体。
继续阅读 → 已知, ,求证; 已知, ,求证。 继续阅读 → Quine指的是一种特殊的程序,其输出内容等于其自身代码。想写出这样的程序需要动一番脑筋。
如下代码即为Python的quine:
s= 's= %s ;print(s %% repr(s))' ;print(s% repr(s))
继续阅读 → 在Android中点击5次“内部版本号”即可激活开发者选项,其中可以设置USB调试等功能。今天我发现了关闭开发者选项的方法,如StackExchange所述,只需清除“设置”应用程序的数据即可。
继续阅读 → 在写yasf项目的过程中遇到这样的需求:为了构造SQL语句,我必须将若干个字符串拼接起来。一个凑合的办法是,定义一个足够大的静态缓冲区,然后使用诸如sprintf
这样的库函数来构造字符串。但是事实上,构造出来的字符串的总长度是没有上限的。因此,若使用固定长度的缓冲区则会带来缓冲区溢出的隐患(C语言最著名的缺陷之一)。
大部分现代的语言的字符串功能都远强于C,字符串拼接更是非常基本的功能。选择C作为开发语言,那就不得不重复造轮子。我总抱着自己能造出更好的轮子的虚幻的指望。经过若干次试验,我得到了一种比较满意的实现方式。于是我把它从yasf项目中抽了出来,做成一个独立的模块,放在GitHub上,命名为ulbuf (__u__ltra __l__ightweight __buf__fer)。
整个实现的代码不足百行,功能也是少之又少。我发现,只有这样才能保证最大的灵活性。
整个库的特点:
简单
API非常简单,共4个函数。其中,2个用于缓冲区的创建和销毁,1个是为了便于使用而提供的便捷函数。因此,最关键的函数只有1个。
API的参数和返回值的形式是经过仔细设计的。
灵活
在C语言中,用户常常通过增/减指针来表示向字符串中增添/删除字符。只要预留了空间,ulbuf仍允许他们这样做。
在最初的实现中,ulbuf还保存了“缓冲区中已含有的字节数”这一个信息。并且只能向缓冲区中追加内容。对于yasf项目而言,已经足够。
现在的实现中,ulbuf只负责管理缓冲区的容量(最多能容纳的字节数),不储存已含有的字节数信息。既能简化实现,又能保证使用的灵活性。这可能是体现“少即是多”的一个例子。
鲁棒
当用户提示ulbuf需要向缓冲区中写入n个字符时,ulbuf会在第n+1个字符的位置写入字符'\0'
。因此,缓冲区的内容永远是合法的C字符串。这样,使用C标准库操作缓冲区永远不会发生内存越界的错误。
协调
ulbuf创建的缓冲区是char *
类型,可以把它当作正常的字符串,提供给诸如strlen
、strchr
这类标准库函数。
只要预留了空间,那么也可以把缓冲区作为输出参数,提供给诸如strcpy
、sprintf
这类函数使用也是完全没有问题的。这类函数会在第n+1个字符的位置添上一个字符'\0'
,仅仅是把ulbuf已经写入的'\0'
覆写了一次,而没有发生内存越界访问。
高效
在我的平台上测试重复追加字符串的操作,ulbuf的运行速度略快于C++ STL的string
类。
因为STL通常是仔细编写的、深度优化的运行库,可见ulbuf的运行效率是很高的。(欲知具体情况,可以编译并运行代码仓库中的example.cc
程序。)
继续阅读 → yasf 是我正在写的一个SQLite的图形前端。目前写好了大体的图形界面。能够读取SQLite数据库(而不能编辑)。
以下为未成品的截图。图中的对话框用于创建新的索引(仅仅是界面)。
继续阅读 → 前天下午打牌,触景生情想起了一个概率论的问题:3个人打一副扑克(A23456789 10 JQK各4张+大小王=54张牌),随机发牌,每人拿18张。设随机变量X表示三个手中的炸弹(指4张点数相同的牌)的总数,求X的分布律。
这样的问题理论上应该可以使用古典概型来求解。但是我想了一会之后就放弃了。为了对这个问题有一个大概的了解,我写了一段程序模拟洗牌和发牌的过程并且统计其中的炸弹个数。
从程序的输出中我得到了一个有趣的结果。下面是某一百万次试验中得到的统计结果:
685121
259261
49135
5935
520
28
0
从上到下依次是X=0,1,2……的频数。6个炸弹以上的情况没有出现。
下面是条形统计图,可见,随着炸弹数增加,频数迅速减小。概率最大的事件是没有炸弹。炸弹个数不超过1个的频率高达94%,这和我平时打牌的常识相符合。
继续阅读 →