星期四, 三月 29, 2007

bash redirect only stderr to pipe

sh$ cat test
#!/bin/sh

echo "testing 1"
echo "testing 2" >&2

sh$ bash test >/dev/null 2>&1
sh$ bash test 2>&1 >/dev/null
testing 2

星期二, 三月 27, 2007

从 txt2tags 进一步认识标记语句

最开始使用标记语句是从王垠介绍的 TeX/LaTeX 开始的,有很长一段时间我都在努力将文档转变成 TeX/LaTeX,但实际上还是比较麻烦的。从 TeX/LaTeX 的学习中,最大的收获是对标记语言的一个重要特性的认识:内容和形式分离

感觉上,TeX/LaTeX 的标记设计得不是非常清楚,所以为了达到形式与内容分离,需要花费大量的精力,自己定义模板,而效果也不是十分理想;为了实现各种功能,可能需要选择大量新的标记和新的模块包(\usepackage),使本身已经陡峭的学习曲线更加让人生畏;而且为了使 TeX/LaTeX 适应中文也相当麻烦(基本上没有什么软件的中文化不麻烦的,我想现在的编码方式还是有问题)。

后来决定转移到 xml docbook。xml 的标记设计相当的标准,而且 docbook 在一开始就实现了形式与内容的分离。通过样式文件 XSL 来改变目标文档(html/PDF 等)的外观,而作者集中精力于编写的内容。

不过我现在却在使用 txt2tags(.t2t 文件),最主要的一个原因是:简单(KISS)。它的标记非常的简单和直观,从人的感观来说,和编辑普通的 txt 文件没有太大区别,因此具有很强的可读性。使用 XML 的好处的确很多,但其学习曲线也颇为陡峭,在企业当中的现实往往是,让人们去花费精力学习 XML,他们可能会宁可选择 Micro$oft Word。使用 .doc 的弊端是很明显的,那就是你没办法版本化,或者说版本化的意义不大(无法比较,无法查知在什么时候、谁做了哪些改动),因而你无法获得集中管理的优势,在多人协作中将造成巨大的重复成本,因为你没办法同步文档。而使用 t2t,最多只需要半个小时的学习就可以开始使用了(看看 sample,实际上几分钟就可以了,倒是设置编辑器的语法高亮花的时间还多一点)——当然,有问题的时候你还是需要去查查手册,一些比较高级的功能也是需要深入学习才能了解(不需要深入太多)。

当然 t2t 的简单也有其局限性,虽然它的确基本上实现了内容与形式分离(因为实在是很简单),但它的标记太少,不适合比较复杂的文档,另外它缺乏 footnote 和 xref(文档应用)以及索引等功能。而且,它也没有明确的部分、章、节的划分,只是一个层次结构而已。

所以,实际上我心里还是比较倾向于 XML,不过考虑到现在公司的实际情况,而且我还需要能够对 xml 的编辑和使用加深一些学习,比如设置 vim 编辑器等,所以 txt2tags 还是一个不错的选择。

目前来看,我只能是在将来再将现在用 t2t 编写的文档转变为 xml docbook(还得找或者写程序,t2t 默认只支持 SGML),而不可能同时使用这两套系统,并且一旦我转变为 XML,就不大有希望再试听 t2t 了。最主要的原因是 txt2tags <=> xml docbook 的标记之间是一个一对多的关系,即一个 txt2tags 标记在 xml docbook 可以多个标记来表示。如果编写 xml docbook 的人使用了 docbook 才有的特性,这些特性要么不能显示,要么干脆会丢失,因为使用 t2t 的人在提交和转换的时候会导致这些信息丢失。

除非我改变 t2t 的标记,在其中保留一些诸如"<"screen">""<"/screen">"的 XML 标记,而扩充的 txt2tags 在处理的时候能够自动忽略这些标记,或通过一个映射表将其转换为某种它能够处理的标记。不过重点还是在 XML,即后台时间实现自动化处理以转换其他文档的还是 XML 文档,即增加了一层,由
txt2tags --> html/pdf/tex
变为
txt2tags --> xml docbook --> html/pdf/tex
。但是所有人仍然必须编辑 t2t 文件而不是同时编辑 t2t 和 xml 文件,否则是无法同步的。

这样做的目的是一方面在最大程度上保持了 t2t 的简单性,同时在 t2t 无法达到要求的时候采用 xml。

关于映射表,参考 txt2tags 源代码(python),可以发现它实际上做了一个标记的映射,将 txt2tags 的标记和其他格式文档的标记进行 dict map。不过它这个映射表是写在代码里面的,这使我考虑是否能将其写在外部,作为一种 schema 而存在。这样不仅能实现上面的转换要求,而且可以实现一些比如自定义标签的功能。

自定义标签?比如假设我已经编写了一个脚本可以处理这些 schema,并且可以做转换,那么我可能会使用如下的命令:
sh$ ctags schema text.t2t >text...
例如 xml 文件:
sh$ ctags xml.schema text.t2t >text.xml
当然还可以使用 html 等,不过我觉得不太好,因为既然 xml 最标准,那么为什么还要其他的呢?直接利用 xml 的威力就好了。并且对同一个 xml 文档,我还可以通过使用不同的映射关系来实现一些差别化的功能...

那么一个 schema 可能的形式如下:
xml_tag_name, src_tag_start, src_tag_end, dst_tag_start, dst_tag_end
verbatim, ``, ``, "<"verbatim">", "<"/verbatim">"

星期五, 三月 23, 2007

txt2tags and its vim highlight

安装很简单,将解压包里的 txt2tags 拷贝到 /usr/local/bin 即可。

为了在编辑器中实现语法高亮,必须做一些调整。对 vim,需要将 syntax highlighting file 拷贝到系统中,如果是 root 用户,可以将 extras/txt2tags.vim 拷贝到诸如 /usr/share/vim/vim63/syntax/,普通用户可以拷贝到 ~/.vim/syntax。在编辑时使用 :set syntax=txt2tags 即可实现 txt2tags 语法高亮。

但为了自动实现,可以在 /usr/share/vim/vim63/filetype.vim 中
  " Z-Shell script
au BufNewFile,BufRead zsh*,zlog* setf zsh
后面加上两行:
  " txt2tags file
au BufNewFile,BufRead *.t2t setf txt2tags
注意在 .vim 文件中 " 表示注释。

普通用户可以直接在 ~/.vimrc 中加入:
  au BufNewFile,BufRead *.t2t set ft=txt2tags


关于 filetype 的说明也可以在 extras/txt2tags.vim 的 INSTALL 一节找到。
~/.vim/syntax
~/.vimrc(normal user)
au BufNewFile,BufRead *.t2t set ft=txt2tags

" INSTALL: (as superuser)
"
" If you have access to the system configuration, edit the
" /usr/share/vim/vim*/filetype.vim file, adding the following
" lines after the 'Z-Shell script' entry (near the end):
"
" " txt2tags file
" au BufNewFile,BufRead *.t2t setf txt2tags
"
" And copy this file (txt2tags.vim) to the Vim syntax dir:
"
" /usr/share/vim/vim*/syntax/


关于其他编辑器,目前没有研究的结果。

AA Center (2), openldap for centrialized login

在前面说明 Kerberos 的时候,已经说明的使用 openldap 的必要性,也就是要集中用户信息,来实现集中登录。这样我不需要在每一个主机上单独维护用户信息了。

openldap 标准的文档在:
http://www.openldap.org/doc/admin23/
应该先阅读这个文档,我这里只说明一些比较特殊不好理解的地方,特别是这个文档中没有说清楚的问题。以及利用基本的知识建立集中用户信息的方法。

有一个中文版:
http://www.infosecurity.org.cn/article/pki/ldap/23484.html

首先来看看 LDAP 的基本原理。LDAP(Lightweight Directory Access Protocol),轻型目录访问协议,从用户的角度来说,可以看成是对一个树形结构的数据库的访问协议,也就是目录服务。

数据模型:
在这个树形结构中,每一个节点是以条目(Entry)来表示的,这相当于 XML 中的 Element。每一个 Entry 最基本的信息就是 DN(Distinguished Name)和RDN(Relative Distinguished Name),用来表示这个节点,类似于相对路径和绝对路径的概念。

每一个 Entry 是一组属性(Attribute)的集合,每一个 Attribute 包含 Value 以及相应的 Type 和 ObjectClass 说明。

ObjectClass 是面向对象的概念,因此每一个 Entry 可以看作一个 Class 的实例(Instance)。而这个 Class 的定义会说明这种类型的 Entry 必须(MUST)包含哪些属性,可能(MAY)包含哪些属性等。Type 的定义与之类似。

RFC2551
Each entry MUST have an objectClass attribute. The objectClass
attribute specifies the object classes of an entry, which along with
the system and user schema determine the permitted attributes of an
entry. Values of this attribute may be modified by clients, but the
objectClass attribute cannot be removed. Servers may restrict the
modifications of this attribute to prevent the basic structural class
of the entry from being changed (e.g. one cannot change a person into
a country).


每一个 Entry 必须包含至少一个 ObjectClass 声明。

那么这些 ObjectClass 和 AttributeType 的声明在什么地方呢?一般都放在 Schema 中。Schema 一般都以文件的形式而存在,例如 /usr/local/etc/openldap/schema,因此与 DocBook 的 DTD 就有些相似了。

可以看一个 Entry 的例子:
dn: uid=rocky,ou=People,dc=shopex,dc=cn
uid: rocky
cn: rocky
objectClass: account
objectClass: posixAccount
objectClass: top
objectClass: shadowAccount
userPassword: {crypt}$1$Kk00o2L8$fKM7c./FLiobXS4I.ktHU1
shadowLastChange: 13524
shadowMax: 99999
shadowWarning: 7
loginShell: /bin/bash
uidNumber: 1000
gidNumber: 1000
homeDirectory: /home/rocky
gecos: rocky
dn 就是前面说的 DN,那么其他那些 uid,o,ou,dc,cn,st,... 等又是表示什么含义呢?随便找一些 LDAP 的技术资料,都会给你一些如上的例子,其中有很多 o,ou,dc,cn 之类的东西,但几乎从来没有什么通俗点的文档解释过这些东西的来历。我怎么知道什么时候要使用那一个呢?

事实上,这些东西都是 AttributeType,在 RFC2253 中,你可以看到如下的说明:

String X.500 AttributeType
------------------------------
CN commonName
L localityName
ST stateOrProvinceName
O organizationName
OU organizationalUnitName
C countryName
STREET streetAddress
DC domainComponent
UID userid


前面已经说过,ObjectClass 和 AttributeType 都是有定义的,定义在 schema 中。那么对这些 AttributeType 的定义就在 /usr/local/etc/openldap/schema/core.shema 中。所以,可以看到,虽然 O,OU,C,CN,DC,UID 这些是很基础的属性类型,也仍然是由外部来定义的。注意:dn 不是由 schema 定义的。

所以在上面那个 Entry 的例子中,除了 objectClass 声明之外,其他都是 Attribute Type 声明,其实质是相同的,所以 cn, uid 与 uidNumber, userPassword 是一样的,只不过那些属性类型的声明不再 core.schema 中,而在 /usr/local/etc/openldap/schema/nis.schema。

上面的 Entry 是有格式的。按照这个格式编写的文件就称之为 LDIF(LDAP Data Interchange Format)文件。那么 ldif 又如何知道要用哪个 schema 的呢?

这在 slapd.conf 中定义。slapd 是 ldap 的服务守护进程。
sh# vi /usr/local/etc/openldap/slapd.conf
include /Opt/LDAP/etc/openldap/schema/nis.schema


用 LDAP 集中登录(Kerberos):
就目 前我所知之,利用 LDAP 集中用户信息以实现登录有两种方案,一是利用 pam_ldap 来实现认证,另一种是利用 Kerberos 替代 pam_ldap 来做认证,而用户信息存放在 LDAP 数据库中。无论采取那种形式,都必须用到 nss_ldap 库,从而可以在 /etc/nsswitch.conf 中增加对 ldap 的使用选项。

配置服务器,修改 slapd.conf,将 suffix 和 rootdn 都改成你自己的域,如下:
sh# slappasswd
{SSHA}ck65VXczIGUsE/EOCYdF8qxwBCf73di7
sh# vi /usr/local/etc/openldap/slapd.conf
# suffix "dc=my-domain,dc=com"
suffix "dc=shopex,dc=cn"
# rootdn "cn=Manager,dc=my-domain,dc=com"
rootdn "cn=ldapadmin,dc=shopex,dc=org"
# rootpw secret
rootpw {SSHA}ck65VXczIGUsE/EOCYdF8qxwBCf73di7
这样,openldap 将以 cn=ldampadmin,dc=shopex,dc=cn 作为你实际的顶级域。rootpw 是服务器密码,是可以通过网络来访问的,默认是明文,所以上面使用 slappasswd 来生成加密串,一旦完成配置,应该注释禁用该条目。openldap 默认使用明文传送密码,除非在 slapd.conf 中配置使用了 SSL/TSL(Transaction Layer Security)。

然后启动服务:
sh# /usr/local/libexec/slapd

sh# /usr/local/libexec/slapd -f /usr/local/etc/openldap/slapd.conf
在继续之前,先看看有没有问题,运行如下命令:
sh# ldapsearch -x -b '' -s base '(objectclass=*)' namingContexts
# extended LDIF
#
# LDAPv3
# base <> with scope baseObject
# filter: (objectclass=*)
# requesting: namingContexts
#

#
dn:
namingContexts: dc=shopex,dc=org

# search result
search: 2
result: 0 Success

# numResponses: 2
# numEntries: 1
然后我需要在 LDAP 的数据库中添加用户信息,这可以通过编写 LDIF 文件,然后用 ldapadd 工具来导入。前面的 Entry 例子即是这样一个包含完整用户信息的一个 LDIF 文件。但如果要将现有的用户信息迁移到 LDAP,那么可以使用一个称之为 MigrationTools 的工具;可能你不太属性这个 LDIF 应该怎么手工编写,那么也可以利用这个工具先生成一个模板,再在那个基础上进行修改。

可以在 http://www.padl.com/download/MigrationTools.tgz 下载最新的版本。然后来看看怎么做:
sh# cd MigrationTools-47/
sh# vi migrate_common.ph
$DEFAULT_BASE = "dc=shopex,dc=cn"
sh# ./migrate_base.pl >/tmp/base.ldif
sh# ./migrate_group.pl /etc/group /tmp/group.ldif
sh# ./migrate_hosts.pl /etc/hosts /tmp/hosts.ldif
sh# ./migrate_passwd.pl /etc/passwd /tmp/passwd.ldif
然后修改这几个文件,特别是 passwd.ldif 和 group.ldif,因为这里我们之做实验,不做实际的迁移,所以我们将上面 Entry 例子中的内容照搬过来,其 他的条目删除——注意,在做实际迁移的时候,因为迁移后很多用户比如 root 不再使用本地 /etc/passwd 中的信息,所以如果中间出错,有可能导致无法登录,所以如果要迁移 root 等重要用户,应该保证有一个 root 登录会话,并且在测试成功之前不要注销!

如果要迁移 root 用户,还有其他一些重要的问题需要考虑。
sh# ldapadd -x -h localhost -D "cn=ldapadmin,dc=shopex,dc=org" -w "secret" -f passwd.ldif
adding new entry "uid=rocky,ou=People,dc=shopex,dc=cn"
ldap_add: Invalid syntax (21)
additional info: objectClass: value #0 invalid per syntax
出现这个错误就是和上面说的那样,应该将 nis.schema 包含进来。
sh# vi /usr/local/etc/openldap/slapd.conf
include /usr/local/etc/openldap/schema/core.schema
include /usr/local/etc/openldap/schema/nis.schema
sh# killall -HUP slapd
# /usr/local/libexec/slapd -f /usr/local/etc/openldap/slapd.conf
/usr/local/etc/openldap/schema/nis.schema: line 203: AttributeType not found: "manager"
这是因为还 nis.schema 还依赖于另一个 schema,consine.schema
sh# vi /usr/local/etc/openldap/slapd.conf
include /usr/local/etc/openldap/schema/core.schema
include /usr/local/etc/openldap/schema/cosine.schema
include /usr/local/etc/openldap/schema/nis.schema
我不太清楚为什么使用加密后的密码不行,所以还是只好先使用了默认的那个密码。
sh# ldapadd -x -h localhost -D "cn=ldapadmin,dc=shoepx,dc=org" -f passwd.ldif -w "{SSHA}ck65VXczIGUsE/EOCYdF8qxwBCf73di7"
ldap_bind: Invalid credentials (49)
如果你在导入时发现如下错误:
sh# /usr/local/libexec/slapd -f /usr/local/etc/openldap/slapd.conf
sh# ldapadd -x -h localhost -D "cn=ldapadmin,dc=shopex,dc=org" -w "secret" -f passwd.ldif
adding new entry "uid=rocky,ou=People,dc=shopex,dc=cn"
ldap_add: Server is unwilling to perform (53)
additional info: no global superior knowledge
那么你应该首先检查 slapd.conf 文件的配置是使用了正确的域,和 migrate_common.ph 中应该是一样的。这里是
rootdn      "cn=ldapadmin,dc=shopex,dc=org"
写错了!

如果没有问题,那么应该是如下的输出:
adding new entry "uid=rocky,ou=People,dc=shopex,dc=cn"
adding new entry "cn=rocky,ou=Group,dc=shopex,dc=cn"
接着配置各个客户端。你应该已经安装 Kerberos 的方法配置了 PAM 认证,所以不需要再做改动,唯一需要更改的就是 /etc/nsswitch.conf 了:
passwd: files ldap
shadow: files ldap
group: files ldap
注意,和前面配置 Kerberos 认证时一样,rocky 这个帐户并不存在与本地的 /etc/passwd 文件中,现在只有在 LDAP 数据库中有他的信息,并且 Kerberos 中有其相应的 Client principal。

从 PuTTY 登录的情况来看:
 login as: rocky
rocky@192.168.0.98's password:
Last login: Tue Mar 20 13:23:46 2007 from 192.168.0.64
Could not chdir to home directory /home/rocky: No such file or directory
-bash-3.00$

sh# tail -f /var/log/message
Mar 21 09:37:53 docs sshd(pam_unix)[3913]: authentication failure; logname= uid=0 euid=0 tty=ssh ruser= rhost=192.168.0.64 user=rocky
Mar 21 09:37:53 docs sshd[3913]: pam_krb5[3913]: The "hosts" configuration directive is not supported with your release of Kerberos. Please check if your release supports an `extra_addresses' directive instead.
Mar 21 09:37:53 docs sshd[3913]: pam_krb5[3913]: authentication succeeds for 'rocky' (rocky@SHOPEX.CN)
Mar 21 09:37:53 docs sshd(pam_unix)[3915]: session opened for user rocky by (uid=0)

sh# tail -f /var/log/krb5kdc.log
Mar 21 09:37:53 docs.shopex.cn krb5kdc[23244](info): AS_REQ (7 etypes {18 17 16 23 1 3 2}) 192.168.0.98: ISSUE: authtime 1174484273, etypes {rep=16 tkt=23 ses=16}, rocky@SHOPEX.CN for krbtgt/SHOPEX.CN@SHOPEX.CN
Mar 21 09:37:53 docs.shopex.cn krb5kdc[23244](info): AS_REQ (7 etypes {18 17 16 23 1 3 2}) 192.168.0.98: ISSUE: authtime 1174484273, etypes {rep=16 tkt=23 ses=16}, rocky@SHOPEX.CN for krbtgt/SHOPEX.CN@SHOPEX.CN
那是不是说我实际上不需要 Kerberos 呢?因为几乎所有的信息都在 LDAP 数据库中。

sh# /etc/init.d/krb5kdc stop
后再尝试登录,则 /var/log/messages 输出如下:

Mar 21 09:42:05 docs sshd(pam_unix)[3972]: authentication failure; logname= uid=0 euid=0 tty=ssh ruser= rhost=192.168.0.64 user=rocky
Mar 21 09:42:05 docs sshd[3972]: pam_krb5[3972]: The "hosts" configuration directive is not supported with your release of Kerberos. Please check if your release supports an `extra_addresses' directive instead.
Mar 21 09:42:05 docs sshd[3972]: pam_krb5[3972]: authentication fails for 'rocky' (rocky@SHOPEX.CN): Authentication service cannot retrieve authentication info. (Cannot contact any KDC for requested realm)
这说明 Kerberos 的验证确实在起作用。而且 LDAP 中的 userPassword 和 Kerberos 的 rocky 用户的 Password 实际上是不一样的

尝试先从 Kerberos 取得票据:
sh# kinit rocky
sh# ssh rocky@docs.shopex.cn
Last login: Wed Mar 21 09:56:11 2007 from docs.shopex.cn
Could not chdir to home directory /home/rocky: No such file or directory
-bash-3.00$ id
uid=1000(rocky) gid=1000(rocky) groups=1000(rocky)

sh# tail -f /var/log/messages
Mar 21 09:56:57 docs sshd(pam_unix)[4129]: session opened for user rocky by (uid=0)
可见,使用 kinit 取得票据之后,登录就不会再有密码提示了。
[root@docs ~]# su - rocky
su: warning: cannot change directory to /home/rocky: No such file or directory
-bash-3.00$
也没有问题。

再看看以其他用户的身份会是什么情况:
sh# su - sysadm
sh$ ssh rocky@docs.shopex.cn
The authenticity of host 'docs.shopex.cn (192.168.0.98)' can't be established.
RSA key fingerprint is 82:25:6e:1f:8e:70:dc:66:62:3e:7b:4c:f0:40:3e:4c.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added 'docs.shopex.cn,192.168.0.98' (RSA) to the list of known hosts.
rocky@docs.shopex.cn's password:
Last login: Wed Mar 21 09:56:57 2007 from docs.shopex.cn
Could not chdir to home directory /home/rocky: No such file or directory
-bash-3.00$

/var/log/messages
Mar 21 09:57:33 docs su(pam_unix)[4148]: session opened for user sysadm by root(uid=0)
Mar 21 09:57:44 docs sshd(pam_unix)[4174]: authentication failure; logname= uid=0 euid=0 tty=ssh ruser= rhost=docs.shopex.cn user=rocky
Mar 21 09:57:44 docs sshd[4174]: pam_krb5[4174]: The "hosts" configuration directive is not supported with your release of Kerberos. Please check if your release supports an `extra_addresses' directive instead.
Mar 21 09:57:44 docs sshd[4174]: pam_krb5[4174]: authentication succeeds for 'rocky' (rocky@SHOPEX.CN)
Mar 21 09:57:44 docs sshd(pam_unix)[4176]: session opened for user rocky by (uid=0)

sh# su - sysadm
sh$ kinit rocky
Password for rocky@SHOPEX.CN:
sh$ klist
Ticket cache: FILE:/tmp/krb5cc_501
Default principal: rocky@SHOPEX.CN

Valid starting Expires Service principal
03/21/07 09:59:52 03/22/07 09:59:52 krbtgt/SHOPEX.CN@SHOPEX.CN

Kerberos 4 ticket cache: /tmp/tkt501
klist: You have no tickets cached
sh$ ssh rocky@docs.shopex.cn
Last login: Wed Mar 21 09:57:44 2007 from docs.shopex.cn
Could not chdir to home directory /home/rocky: No such file or directory
-bash-3.00$

/var/log/messages
Mar 21 10:00:32 docs sshd(pam_unix)[4234]: session opened for user rocky by (uid=0)
Mar 21 10:18:44 docs su[4629]: nss_ldap: reconnecting to LDAP server...
Mar 21 10:18:44 docs su[4629]: nss_ldap: reconnected to LDAP server after 1 attempt(s)

注意上面的 FILE:/tmp/krb5cc_501, 可以发现是以用户的 UID 来命名的。

然后看看增加一个 Kerberos 用户 roc,并从这个用户登录到 rocky 是否可行:
sh# kadmin -p rocky/admin@SHOPEX.CN
Authenticating as principal rocky/admin@SHOPEX.CN with password.
Password for rocky/admin@SHOPEX.CN:
kadmin: addprinc roc
WARNING: no policy specified for roc@SHOPEX.CN; defaulting to no policy
Enter password for principal "roc@SHOPEX.CN":
Re-enter password for principal "roc@SHOPEX.CN":
Principal "roc@SHOPEX.CN" created.
kadmin: quit
sh# kinit roc
Password for roc@SHOPEX.CN:
sh# klist
Ticket cache: FILE:/tmp/krb5cc_0
Default principal: roc@SHOPEX.CN

Valid starting Expires Service principal
03/21/07 10:16:05 03/22/07 10:16:05 krbtgt/SHOPEX.CN@SHOPEX.CN

Kerberos 4 ticket cache: /tmp/tkt0
klist: You have no tickets cached

sh# ssh rocky@docs.shopex.cn
rocky@docs.shopex.cn's password:
Last login: Wed Mar 21 10:19:32 2007 from docs.shopex.cn
Could not chdir to home directory /home/rocky: No such file or directory
-bash-3.00$

Mar 21 10:20:46 docs sshd(pam_unix)[4745]: authentication failure; logname= uid=0 euid=0 tty=ssh ruser= rhost=docs.shopex.cn user=rocky
Mar 21 10:20:46 docs sshd[4745]: pam_krb5[4745]: The "hosts" configuration directive is not supported with your release of Kerberos. Please check if your release supports an `extra_addresses' directive instead.
Mar 21 10:20:46 docs sshd[4745]: pam_krb5[4745]: authentication succeeds for 'rocky' (rocky@SHOPEX.CN)
Mar 21 10:20:46 docs sshd(pam_unix)[4747]: session opened for user rocky by (uid=0)
这说明 Kerberos 的 User principal 也还是必须和 LDAP 的 User infomation 互相匹配,否则仍然是不能登录的。

方案比较,以及为什么选择 Kerberos:
前面说过,除了使用 Kerberos 的 pam_krb5 来进行 Authentication(这也是上一篇中使用的方法)之外,另一种选择是使用 pam_ldap。看起来,如果使用 pam_ldap 似乎要简单的多,因为只使用一套软件就解决问题了。其实不然。因为这是在做身份验证,所以安全性是非常重要的问题,那么加密就必不可少,而 openldap 默认是使用明文加密的。可以看下面这个例子:
sh# ldapsearch -x -b 'uid=rocky,ou=People,dc=shopex,dc=cn'
# extended LDIF
#
# LDAPv3
# base with scope subtree
# filter: (objectclass=*)
# requesting: ALL
#

# rocky, People, shopex.cn
dn: uid=rocky,ou=People,dc=shopex,dc=cn
uid: rocky
cn: rocky
objectClass: account
objectClass: posixAccount
objectClass: top
objectClass: shadowAccount
userPassword:: e2NyeXB0fSQxJEtrMDBvMkw4JGZLTTdjLi9GTGlvYlhTNEkua3RIVTE=
shadowLastChange: 13524
shadowMax: 99999
shadowWarning: 7
loginShell: /bin/bash
uidNumber: 1000
gidNumber: 1000
homeDirectory: /home/rocky
gecos: rocky

# search result
search: 2
result: 0 Success

# numResponses: 2
# numEntries: 1
这里可以看到,其他的用户信息无所谓,但 userPassword 是可以毫无障碍的被任何人查询的,如果密码确实存放在 LDAP 中,那就危险了。

Kerberos 默认是加密的,而 openldap 要实现加密则必须使用 SSL/TLS,这也就意味着你必须为每一台客户系统维护一对私钥/证书,在这里也可以看到 Kerberos 的一个好处,那就是票据的加密是由 Kerberos 自动维护的。这并不是说就不再需要 SSL/TLS 了,不过在认证这一块,Kerberos 确实更方便。

而且,你不再需要频繁的输入密码了!

所以这里最终的效果就是,openldap 充当了 /etc/passwd 的角色,而 Kerberos 相当于 /etc/shadow。

/---------->KDC
| /------KDC
(1) |
| (2)
| |
| V
Client------(3)------>Server(4)------(5)------>LDAP
(4) Lookup /etc/passwd and /etc/nsswitch.conf
(5) Lookup LDAP Database for account information(NOT Password)

星期四, 三月 22, 2007

php_admin_value for security

看一个 PHP 程序:
$fp = fopen("/etc/passwd","r"); 
if($fp) {
echo 'ok!';
$result = fread($fp, 8192);
echo $result;
} else {
echo 'no!';
}
?>
通过 URL 运行这个 PHP 程序,会发现程序确实可以读到 /etc/passwd。这是一个危险的信号,我没有想到 PHP 会有这样的安全问题(这应该不是 Apache 的问题,因为 DocumentRoot 已经限制了根路径,而且又没有使用 FollowSymLink 选项)。这意味着一个 PHP 程序可以访问文件系统的任何地方!

当提供虚拟主机服务的时候,这个问题就会变得更加棘手,因为各个虚拟主机可能属于不同的用户,而在这种情况下,一个用户将可以毫无障碍的读取另一个用户的文件!

解决的办法是使用 PHP 的 safemode 中的 open_basedir 参数来进行限制,并且需要对每一个虚拟主机分别做不同的限制:
"<"VirtualHost *:80">" 
DocumentRoot "/var/www/html/docs/html"
php_admin_value open_basedir /var/www/html/docs/html/
"<"Directory "/var/www/html/docs/html"">"
Options Indexes
AllowOverride None
Order allow,deny
Allow from all
"<"/Directory">"
"<"/VirtualHost">"
也可以在 php.ini 中更改 open_basedir 参数来做一个全局的限制。如 open_basedir = /www/users:/tmp 等。

这种方法只是正对 mod_php 有效,对使用 CGI 方式(如 cgiwrap)则不行——如果没有使用 LoadModule mod_php,那么在 httpd.conf 中使用 php_admin_value 将使得 apache 无法启动。

但是如果是使用 mod_rewrite 方式建立的虚拟主机呢?

星期三, 三月 21, 2007

AA Center (1), Kerberos

AA 即 Authentication 和 Authorization,认证和授权。在企业的架构体系中,主要的目标就是要能够统一帐户和认证信息,比如我希望 http svn, ssh login, samba 等都可以使用同一个帐户来登录,而不是为每一个服务创建不同的帐户,你当然可以使用同样的用户名的密码,但实际上这些帐户却并不同步,修改一个并不能同时修改另一个,而且各个服务的密码加密方法不同,有些服务的密码强度不够,甚至使用明文传送,那么根据最短板原理,你整个系统的安全性就大有问题了。

授权是另一个方面的问题,以决定一个帐户能做什么。对于集中认证机制来说,应该就是我可以使用哪些服务了。

我打算采用 Kerberos 来实现集中认证。关于 Kerberos 的基本原理,可以参考Kerberos 的原理。这篇文章比较有趣,但为了更清楚的说明,做一个图来说明。

Kerberos 是一个三方认证体系,所以有 KDC, Client 和 Server 三台主机吧

/---------->KDC
| /------KDC
(1) |
| (2)
| |
| V
Client------(3)------>Server
(1) Authentication REQUEST to KDC
(2) KDC_REPLAY
KDC_REPLAY = TICKET, OTHER
OTHER = {client, server, K_session}K_user
TICKET = {client, server, start_time, lifetime, K_session}K_Server
(3) Authentication REQUEST to Server
REQUEST = AUTHENTICATOR, TICKET
AUTHENTICATOR = {user, addr}K_session
也就是说,KDC 和 Server 之间并没有直接联系,而是分享了一个共同的秘密,即在 TICKET 中的 K_session。Client 得到 TICKET 的时候,他不能更改其中的 K_session,因为它没有 K_Server,但它可以拿到 OTHER 中的 K_session,并用这个生成验证器 AUTHENTICATOR,它把验证器和票一起提交给 Server。而 Server 拿到 TICKET 后,可以用 K_Server 解密,并取出 K_session,并比较 AUTHENTICATOR 和 TICKET 中的内容并确保一只,以及 TICKET 中的时间没有过期。如果在(2)时用户更改了 K_session,那么 Client 生成的 AUTHENTICATOR 将无法被解密,这也就意味着这个 Client 无法通过 Server 的验证了。

下面看看 Kerberos 的基本配置。主要的软件包有 krb5-libs, krb5-server, krb5-workstation 以及 pam_krb5。

编辑 /etc/krb5.conf,将其中[realms]和[domain_realm]部分的 EXAMPLE.COM 的内容全部替换成自己的域名,例如:
[realms]
SHOPEX.CN = {
kdc = kdc.shopex.cn:88
admin_server = kdc.shopex.cn:749
default_domain = shopex.cn
}

[domain_realm]
.shopex.cn = SHOPEX.CN
shopex.cn = SHOPEX.CN
[kdc]
profile = /var/kerberos/krb5kdc/kdc.conf
然后保证 admin_server 即 kdc.shopex.cn 可以被解析,可以使用 DNS 或 /etc/hosts。

在上面的[kdc]部分,指明了 KDC 使用的配置文件,修改[realms]为自己的域。

然后创建数据库:
sh# /usr/kerberos/sbin/krb5_util create -r SHOPEX.CN -s
-r 指定 realm,-s 指定创建 stash 文件。stash 文件是主密钥的一个本地加密副本,主密钥用于自动验证 KDC,作为系统的启动序列的一节。这个命令在 kdc.conf 的[kdcdefaults]指定的目录 /var/kerberos/krb5kdc 中创建 Kerberos 数据库文件 principal.db 和 principal.ok,管理数据库文件 principal.kadm5,lock 和 stash(隐藏)等。

然后要编辑 /var/kerberos/krb5kdc/kadmin.acl,ACL 即 Access Control List(访问控制列表),这个文件控制对 Kerberos 自身数据库的访问权限,访问数据库使用 kadmin 程序,这是一个交互式程序,以便进行增删用户等操作。默认内容是
*/admin@SHOPEX.CN       *
一行,但通常我们会使用其他用户,所以增加:
rocky/admin@SHOPEX.CN   *
这样 rocky/admin@SHOPEX.CN 就用后了所有的管理权限,可以在 kadmin 中运行其所有的命令。如果不增加 rocky/admin 的权限,那么运行 kadmin 就会出现如下问题:
sh# kadmin
Authenticating as principal rocky/admin@SHOPEX.CN with password.
Password for rocky/admin@SHOPEX.CN:
kadmin: addprinc -randkey host/doc.shopex.cn
WARNING: no policy specified for host/doc.shopex.cn@SHOPEX.CN; defaulting to no policy
add_principal: Operation requires ``add'' privilege while creating "host/doc.shopex.cn@SHOPEX.CN".
不论 rocky/admin 是否用后 admin 权限,你都必须先创建这个用户。不过在 Kerberos 中并不称为用户,而是称为 Client 的 principal,principal 既可以是对 Client 的,也可以是对 Server 的,这在后面谈到。

因为还没有其他 principal,而且现在还没有启动 Kerberos 的 KDC 服务,所以不可能运行 kadmin。那么要创建这个 principal,只能运行 kadmin.local,这个程序只能在 KDC 上运行。而 kadmin 可以在 Kerberos Client 上运行以连接到 KDC。

那么运行:
sh# /usr/kerberos/sbin/kadmin.local
kadmin.local: addprinc rocky/admin
...
kadmin.local: addprinc rocky
...
后面这一次是增加一个"普通用户",其完整的 principal 即为 rocky@SHOPEX.CN(或 rocky/@SHOPEX.CN,后面为空)。

接着可以启动 KDC 和与之相关的其他服务了:
sh# /etc/init.d/krb5kdc start
sh# /etc/init.d/kadmin start
sh# /etc/init.d/krb524 start
然后看看客户端能否正常地从 KDC 上取得票据:

sh# kinit rocky
Password for rocky@SHOPEX.CN:
sh# klist
Ticket cache: FILE:/tmp/krb5cc_0
Default principal: rocky@SHOPEX.CN

Valid starting Expires Service principal
03/21/07 13:27:54 03/22/07 13:27:54 krbtgt/SHOPEX.CN@SHOPEX.CN


Kerberos 4 ticket cache: /tmp/tkt0
klist: You have no tickets cached
这个程序是在 KDC 上运行的,是属于 krb5-workstation 包的,因为客户端和 KDC 使用同样的配置文件 /etc/krb5.conf,所以如果在 KDC 上运行不需要额外再配置。整个过程即可以认为是前面图中的(1)(2)部分。上面可以看到已经拿到了票据,cache 在 /tmp/krb5cc_0 中,因为文件权限设定,所以不可能为其他用户所用,保证了安全性。

配置客户端很简单,安装好 krb5-workstation,然后把 KDC 上的 /etc/krb5.conf 拷贝过来即可。当然还有一步,就是前面提到的为 Server 增加一个 principal,为了映像和理解的深刻,先来看看不这么做的情况。

我们使用 telnet 的 Kerberos 感知版本 krb5-telnet 来做这个实验。编辑 /etc/xinetd.d/krb5-telnet,disable = no,重启 xinted,然后用 telnet 登录(PATH=/usr/kerberos/sbin:/usr/kerberos/bin:/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin:/usr/X11R6/bin:/root/bin)
[root@docs ~]# kinit rocky
......
[root@docs ~]# telnet -a docs.shopex.cn
Trying 192.168.0.98...
Connected to docs.shopex.cn (192.168.0.98).
Escape character is '^]'.
[ Kerberos V5 refuses authentication because telnetd: krb5_rd_req failed: No such file or directory ]
[ Kerberos V5 refuses authentication because telnetd: krb5_rd_req failed: No such file or directory ]

[root@docs ~]# telnet docs.shopex.cn
Trying 192.168.0.98...
Connected to docs.shopex.cn (192.168.0.98).
Escape character is '^]'.

docs.shopex.cn (Linux release 2.6.14.2 #1 SMP Thu Jan 11 15:39:36 EST 2007) (3)

login: rocky
Password for rocky:
Login incorrect
login: test
Password for test:
login: Client not found in Kerberos database while getting initial credentials
Last login: Thu Mar 15 18:38:58 from docs
[test@docs ~]$ exit

[root@docs ~]# klist
Ticket cache: FILE:/tmp/krb5cc_0
Default principal: rocky@SHOPEX.CN

Valid starting Expires Service principal
03/15/07 18:20:52 03/16/07 18:20:49 krbtgt/SHOPEX.CN@SHOPEX.CN
03/15/07 18:37:30 03/16/07 18:20:49 host/docs.shopex.cn@SHOPEX.CN


Kerberos 4 ticket cache: /tmp/tkt0
klist: You have no tickets cached
相应的,在 /var/log/krb5kdc.log 中会看到类似的日志信息:
Mar 16 11:12:26 docs.shopex.cn krb5kdc[23244](info): TGS_REQ (7 etypes {18 17 16 23 1 3 2}) 192.168.0.98: UNKNOWN_SERVER: auth
time 1174055114, rocky@SHOPEX.CN for krbtgt/LOCALDOMAIN@SHOPEX.CN, Server not found in Kerberos database
Mar 16 11:12:26 docs.shopex.cn krb5kdc[23244](info): TGS_REQ (7 etypes {18 17 16 23 1 3 2}) 192.168.0.98: UNKNOWN_SERVER: auth
time 1174055114, rocky@SHOPEX.CN for krbtgt/LOCALDOMAIN@SHOPEX.CN, Server not found in Kerberos database

分析一下这整个过程,前面的登录都失败了,因为按照要求,Kerberos 是使用票据来进行认证的,所以不应该在登录时会提示需要密码,因为你已经在 kinit 获取票据的时候输入了密码验证信息。后面再使用 klist 查看票据时,你会发现多了一行,即 host/docs.shopex.cn@SHOPEX.CN,说明已经想 host/docs.shopex.cn@SHOPEX.CN 发送了票据,那为什么不能通过认证呢?


回顾原理,Client, Server 和 KDC 之间必须共享一个 K_session,而 Server 要取得在 TICKET 中的 K_session,它必须拥有 K_Server,但是我们什么时候创建了这个 K_Server 了呢?没有。这就是前面提到的要为这个 Server 增加一个 principal 的含义。


Additionally, the host/server@REALM principal must be in the KDC database, and its key must be stored in /etc/krb5.keytab on the server.


Kerberos applies a default authorization rule: if host H is in realm R, the Kerberos principal u@R is allowed access to the account u@H. Using this default rule implies that the system administrators are managing the correspondence between operating system (OS) usernames and Kerberos principals.


参见:"Kerberos and SSH"

那么首先增加这个 principal
sh# kadmin
kadmin: addprinc host/docs.shopex.cn
...
会提示输入以生成密码,即 K_Server。这个命令可以在 Server 上运行,也可以在 KDC 上运行。然后要将这个 Key 保存早 Server 上,这样 Server 才能使用它来解密 TICKET。
kadmin:  ktadd -k /etc/krb5.keytab host/docs.shopex.cn
Entry for principal host/docs.shopex.cn with kvno 4, encryption type ArcFour with HMAC/md5 added to keytab WRFILE:/etc/krb5.keytab.
Entry for principal host/docs.shopex.cn with kvno 4, encryption type Triple DES cbc mode with HMAC/sha1 added to keytab WRFILE:/etc/krb5.keytab.
Entry for principal host/docs.shopex.cn with kvno 4, encryption type DES with HMAC/sha1 added to keytab WRFILE:/etc/krb5.keytab.
Entry for principal host/docs.shopex.cn with kvno 4, encryption type DES cbc mode with RSA-MD5 added to keytab WRFILE:/etc/krb5.keytab.
kadmin: quit
下面看效果
[root@docs ~]# kinit
Password for rocky@SHOPEX.CN:
[root@docs ~]# klist
Ticket cache: FILE:/tmp/krb5cc_0
Default principal: rocky@SHOPEX.CN

Valid starting Expires Service principal
03/16/07 09:40:34 03/17/07 09:40:28 krbtgt/SHOPEX.CN@SHOPEX.CN

Kerberos 4 ticket cache: /tmp/tkt0
klist: You have no tickets cached
[root@docs ~]# telnet -al rocky docs.shopex.cn
Trying 192.168.0.98...
Connected to docs.shopex.cn (192.168.0.98).
Escape character is '^]'.
[ Kerberos V5 accepts you as ``rocky@SHOPEX.CN'' ]
Password for rocky:
Login incorrect
login: rocky
Password for rocky:
Login incorrect

[root@docs ~]# klist
Ticket cache: FILE:/tmp/krb5cc_0
Default principal: rocky@SHOPEX.CN

Valid starting Expires Service principal
03/16/07 09:40:34 03/17/07 09:40:28 krbtgt/SHOPEX.CN@SHOPEX.CN
03/16/07 09:40:59 03/17/07 09:40:28 host/docs.shopex.cn@SHOPEX.CN

Kerberos 4 ticket cache: /tmp/tkt0
klist: You have no tickets cached
相应的 /var/log/krb5kdc.log 如下
Mar 16 09:40:59 docs.shopex.cn krb5kdc[21498](info): TGS_REQ (1 etypes {1}) 192.168.0.98: ISSUE: authtime 1174052434, etypes {rep=16 tkt=23 ses=1}, rocky@SHOPEX.CN for host/docs.shopex.cn@SHOPEX.CN
Mar 16 09:40:59 docs.shopex.cn krb5kdc[21498](info): TGS_REQ (1 etypes {1}) 192.168.0.98: ISSUE: authtime 1174052434, etypes {rep=16 tkt=23 ses=1}, rocky@SHOPEX.CN for host/docs.shopex.cn@SHOPEX.CN
你可以看到"Kerberos V5 accepts you as ``rocky@SHOPEX.CN''",这说明 Kerberos 认证已经通过了,但是为什么还是不能成功的登录呢(注意,本地系统中是没有 rocky 这个帐户的!)?我们下面通过 ssh 的 pam_krb5 认证方式来详细说明这一点。

要配置 ssh 使用 Kerberos 认证,必须使用 pam_krb5 模块,对于 RedHat 系统,可以使用如下方式来配置:
sh# authconfig
sh# cat /etc/pam.d/system-auth
#%PAM-1.0
# This file is auto-generated.
# User changes will be destroyed the next time authconfig is run.
auth required /lib/security/$ISA/pam_env.so
auth sufficient /lib/security/$ISA/pam_unix.so likeauth nullok
auth sufficient /lib/security/$ISA/pam_krb5.so use_first_pass
auth required /lib/security/$ISA/pam_deny.so

account required /lib/security/$ISA/pam_unix.so broken_shadow
account sufficient /lib/security/$ISA/pam_succeed_if.so uid < 100 quiet
account [default=bad success=ok user_unknown=ignore] /lib/security/$ISA/pam_krb5.so
account required /lib/security/$ISA/pam_permit.so

password requisite /lib/security/$ISA/pam_cracklib.so retry=3
password sufficient /lib/security/$ISA/pam_unix.so nullok use_authtok md5 shadow
password sufficient /lib/security/$ISA/pam_krb5.so use_authtok
password required /lib/security/$ISA/pam_deny.so

session required /lib/security/$ISA/pam_limits.so
session required /lib/security/$ISA/pam_unix.so
session optional /lib/security/$ISA/pam_krb5.so
然后我使用 ssh 来登录试试看。结果当然是不能成功。查看日志的情况:
Mar  8 14:18:18 docs sshd(pam_unix)[23506]: check pass; user unknown
Mar 8 14:18:18 docs sshd(pam_unix)[23506]: authentication failure; logname= uid=0 euid=0 tty=ssh ruser= rhost=192.168.0.64
Mar 8 14:18:18 docs sshd[23506]: pam_krb5[23506]: The "hosts" configuration directive is not supported with your release of Kerberos. Please check if your release supports an `extra_addresses' directive instead.
Mar 8 14:18:18 docs sshd[23506]: pam_krb5[23506]: error resolving user name 'rocky' to uid/gid pair
Mar 8 14:18:18 docs sshd[23506]: pam_krb5[23506]: error getting information about 'rocky'
可以看到,pam_unix 模块找不到用户 rocky,而 pam_krb5 也找不到,但并不是找不到用户,而是找不到与这个用户相对应的 uid/gid 匹配信息

那么做这个事情试一下:useradd rocky。然后使用 ssh 登录并监视日志的情况:

Mar 16 10:55:42 docs sshd(pam_unix)[23726]: authentication failure; logname= uid=0 euid=0 tty=ssh ruser= rhost=192.168.0.64 user=rocky
Mar 16 10:55:42 docs sshd[23726]: pam_krb5[23726]: The "hosts" configuration directive is not supported with your release of Kerberos. Please check if your release supports an `extra_addresses' directive instead.
Mar 16 10:55:42 docs sshd[23726]: pam_krb5[23726]: authentication succeeds for 'rocky' (rocky@SHOPEX.CN)
Mar 16 10:55:42 docs sshd(pam_unix)[23728]: session opened for user rocky by (uid=0)
更进一步,还可以再看看 telnet 的登录情况:
[root@docs ~]# telnet -al rocky docs.shopex.cn
Trying 192.168.0.98...
Connected to docs.shopex.cn (192.168.0.98).
Escape character is '^]'.
[ Kerberos V5 accepts you as ``rocky@SHOPEX.CN'' ]
Last login: Fri Mar 16 11:12:19 from docs
[rocky@docs ~]$ exit

[root@docs ~]# telnet -al rocky 192.168.0.98
Trying 192.168.0.98...
Connected to docs.shopex.cn (192.168.0.98).
Escape character is '^]'.
[ Kerberos V5 accepts you as ``rocky@SHOPEX.CN'' ]
Last login: Fri Mar 16 11:13:25 from docs
[rocky@docs ~]$ exit

[root@docs ~]# telnet -al rocky 127.0.0.1
Trying 127.0.0.1...
Connected to localhost.localdomain (127.0.0.1).
Escape character is '^]'.
Password for rocky:

[root@docs ~]# cat /etc/hosts
192.168.0.98 test.shopex.cn
[root@docs ~]# telnet -al rocky test.shopex.cn
Trying 192.168.0.98...
Connected to test.shopex.cn (192.168.0.98).
Escape character is '^]'.
[ Kerberos V5 accepts you as ``rocky@SHOPEX.CN'' ]
Last login: Fri Mar 16 11:13:56 from docs
[rocky@docs ~]$
由此可见,虽然我们在 Kerberos 中增加了 rocky 的 principal,但由于 pam_krb5 无法向登录的 login 程序提供用户信息的其他方面,比如 UID/GID, HOME, SHELL, EXPIRE TIME 等这些设定,所以登录会失败。

因此登录包括了两个过程,一是 Authentication,然后是 user information retrival。但是如果我在 Server 本地系统中增加 rocky 这个用户,那就意味着我要集中用户信息的管理方式无法实现。而 Kerberos 显然是不可能提供这些用户信息的。怎么办?

所以,最后归根结底,我们还是需要一个集中用户认证信息的服务。NIS 是不会采用了,那么就只能选择 LDAP 了。使用 openldap 吧。

星期二, 三月 20, 2007

bind zone with 'view'

在前面的"变态"DNS 中说明了针对不同网域使用不同的 DNS 查询结果的设置方法,现在我需要增加一个域,但是不需要这种区别对待的设置。直接编写一个 zone 的设定,named 不能正确启动,检查配置:
sh# named-checkconf etc/namedb/named.conf
etc/namedb/named.conf:20: when using 'view' statements, all zones must be in views
于是改成如下的配置:
view "all" {
match-clients { any; };
zone "." {
type hint;
file "named.ca";
};
zone "0.0.127.in-addr.arpa" {
type master;
file "named.local";
};
zone "groups.shopex.cn" {
type master;
file "groups.shopex.cn.zone";
allow-update { none; };
};
};
这时可以正常启动 named 了,但域名查询却出了问题:
sh$ dig -t NS groups.shopex.cn

; <<>> DiG 9.2.4 <<>> -t NS groups.shopex.cn
;; global options: printcmd
;; connection timed out; no servers could be reached
如果把这部分 view 放在最前面,则以前的 DNS 记录也会受影响而不能正确查询。
sh$ dig n01.shopex.cn

; <<>> DiG 9.2.4 <<>> n01.shopex.cn
;; global options: printcmd
;; connection timed out; no servers could be reached
正确的解法是必须在每个 view 里面包含所有的域!

mailman mailing list

我需要在公司建立一个邮件列表,或者说我需要在自己的企业架构中建立邮件列表,作为团队开发以及用户讨论组等使用。当然至少到目前来看,没有考虑过它会成为基础设施的一部分,我想它应该还是属于上层建筑的。

why not google group?

因为企业的邮箱和邮件列表,特别是内部事务如开发等不能这样做,即使设置了限制,但问题是一方面无法与现有的内部帐号整合在一起,另一方面,如果由于某种原因导致 google group 不能访问(比如年初的地震或者 GFW),则公司的事务将收到影响!所以最终我们需要自己的 mailing list。目前最流行的就是 mailman 了,用 python + C 编写的!

mailman mailing list 有两种接口:web interface && mail interface。及通过 web 页面的表框操作或通过 email 发送诸如 subscribe, unsubscribe 等命令。

安装:
cat .config
pkgname = "mailman";
version = "2.1.9";
user = "mailman";
groups = "web";
group = "mailman";
archive = "mailman-2.1.9.tgz";
command = "tar xfz mailman-2.1.9.tgz";
command = "cd mailman-2.1.9";
command = "./configure --without-permcheck --with-cgi-gid=httpd";
command = "make";
command = "make install";
command = "cd ..";
command = "rm -rf mailman-2.1.9";
time = "20070313 14:01:47 Tue";
使用 rpm 安装则 cgi-gid 比较死,只能使用 apache 用户,只好用源代码编译了。

然后编辑 httpd.conf:
"<"VirtualHost *:80">"
ServerName test.shopex.cn
DocumentRoot "/var/www/html/test"
ScriptAlias /mailman/ /usr/local/mailman/cgi-bin/
Alias /pipermail/ /usr/local/mailman/archives/public
Alias /icons/ /usr/local/mailman/icons/
# AddHandler cgi-script .cgi .py
"<"Directory "/usr/local/mailman/icons/"">"
Options Indexes
Order allow,deny
Allow from all
"<"/Directory">"
"<"Directory "/usr/local/mailman/cgi-bin/"">"
Options Indexes
Order allow,deny
Allow from all
"<"/Directory">"
"<"/VirtualHost">"
然后访问 http://test.shopex.cn/mailman/listinfo(注意修改 hosts 文件),这里 listinfo 实际上是 /usr/local/mailman/cgi-bin/listinfo,是一个二进制的可执行程序!这时可以看到一个页面,并且报告没有创建任何 mailing list。

邮件列表的基本原理就是利用了邮件服务器的 alias 功能。所以使用手工的方法,也可以建立简单的邮件列表,但 Mailman(以前比较流行 Majordomo)可以自动完成大量工作!

将 mailman 和 postfix 集成
sh# vi $prefix/Mailman/mm_cfg.py
MTA = "Postfix"
sh# cd /usr/local/mailman
sh# bin/genaliases
sh# chown mailman:mailman data/aliases*
sh# chmod g+w data/aliases*

sh# vi /etc/postfix/main.cf
myhostname = mail.shopex.cn
mydomain = shopex.cn
myorigin = $mydomain
inet_interfaces = all
mydestination = $myhostname, localhost.$mydomain, localhost,
mail.$mydomain, www.$mydomain, ftp.$mydomain
mynetworks = 192.168.0.0/24, 127.0.0.0/8
alias_maps = hash:/etc/aliases, hash:/usr/local/mailman/data/aliases
alias_database = hash:/etc/aliases, hash:/usr/local/mailman/data/aliases


记得要运行 newaliases,并且是 postfix 的 newaliases,RHEL4 可以用:
sh# alternatives --set mta /usr/sbin/sendmail.postfix
来指定。

然后要创建实际的邮件列表。邮件列表既可以使用 command line 的操作命令,也可以从 web 页面上运行 cgi 脚本来实现。下面分别介绍。
sh# /usr/local/mailman/bin/newlist
输入邮件列表的名字,会自动生成如下的邮件列表别名文件的内容。
sh# cat /usr/local/mailman/data/aliases
# This file is generated by Mailman, and is kept in sync with the
# binary hash file aliases.db. YOU SHOULD NOT MANUALLY EDIT THIS FILE
# unless you know what you're doing, and can keep the two files properly
# in sync. If you screw it up, you're on your own.

# The ultimate loop stopper address
mailman-loop: /usr/local/mailman/data/owner-bounces.mbox

# STANZA START: dev
# CREATED: Tue Mar 13 18:40:13 2007
dev: "|/usr/local/mailman/mail/mailman post dev"
dev-admin: "|/usr/local/mailman/mail/mailman admin dev"
dev-bounces: "|/usr/local/mailman/mail/mailman bounces dev"
dev-confirm: "|/usr/local/mailman/mail/mailman confirm dev"
dev-join: "|/usr/local/mailman/mail/mailman join dev"
dev-leave: "|/usr/local/mailman/mail/mailman leave dev"
dev-owner: "|/usr/local/mailman/mail/mailman owner dev"
dev-request: "|/usr/local/mailman/mail/mailman request dev"
dev-subscribe: "|/usr/local/mailman/mail/mailman subscribe dev"
dev-unsubscribe: "|/usr/local/mailman/mail/mailman unsubscribe dev"
# STANZA END: dev
实际上首先要创建 site-wild mailing list:
site-wild mailing list:
sh# bin/newlist mailman
sh# bin/config_list -i data/sitelist.cfg mailman
通过如下地址访问邮件列表信息:
http://test.shopex.cn/mailman/listinfo/dev
看看如果不存在的列表的情况:
http://test.shopex.cn/mailman/listinfo/users
"No such list users"

下面设置密码,除了 list owner 之外,有两个特殊用户,即 Administrator 和 list creator,前者相当于 root,后者一般专门用来从 web page 创建 list 的时候使用。这两者分别用如下命令创建密码:
sh# bin/mmsitepass
sh# bin/mmsitepass -c
为了能够从页面访问,必须启动 mailman 服务,实际上会启动一个名为 qrunner 的进程。对 RHEL4,使用:
sh# cp scripts/mailman /etc/init.d/mailman
sh# chkconfig --add mailman
sh# chkconfig mailman on
sh# chkconfig sendmail off
sh# chkconfig postfix on
那么现在可以打开页面 http://test.shopex.cn/mailman/create 来建立邮件列表。在最下面使用 list creator 密码提交。

hostname settings 会产生影响。例如 http://test.shopex.cn/mailman/listinfo,有显示
"If you are having trouble using the lists, please contact mailman@test1.shopex.cn."(这里我更改了主机名),显然,这个值根据主机名而改变了。但是这不是我希望的值。

这里先不考虑虚拟主机的情况,那么需要修改 mm_cfg.py,增加 DEFAULT_EMAIL_HOST 和 DEFAULT_URL_HOST。这两个参数在安装时会写入到 /usr/local/mailman/Mailman/Defaults.py 中,但不要直接编辑这个文件,编辑 /usr/local/mailman/Mailman/mm_cfg.py 就好了。

比如在 Defaults.py 中的默认设定为:
DEFAULT_EMAIL_HOST = 'docs.shopex.cn'
DEFAULT_URL_HOST = 'docs.shopex.cn'
这会有问题,因为我在 apache 上发布的虚拟主机名是 test.shopex.cn,并且我也不希望用户把邮件发送到 users@docs.shopex.cn 或 users@test.shopex.cn 这样的地址,而是希望是 users@shopex.cn 这样的地址(即 mailing list 的地址将是 listname@DEFAULT_EMAIL_HOST)。那么我编辑 mm_cfg.py
DEFAULT_EMAIL_HOST = 'shopex.cn'
DEFAULT_URL_HOST = 'test.shopex.cn'
add_virtualhost(DEFAULT_URL_HOST, DEFAULT_EMAIL_HOST)
这里 DEFAULT_URL_HOST 不能写成"shopex.cn",否则你在页面上访问不行了,因为链接到错误的域名上去了。通常 URL_HOST 和 EMAIL_HOST 会不同,如上。因为 EMAIL_HOST 通常是你的整个域的,而 postfix 可以为整个域接收和发送邮件,但 URL_HOST 则是具体的主机的,如果设置成 shopex.cn,则在 http://test.shopex.cn/mailman/listinfo/dev 进行 subscribe 的时候,会连接到 http://shopex.cn/mailman/subscribe/dev,但这个可能是不存在的,或根本不是统一台主机(除非确实是同一台主机并且对 shopex.cn 域做了同样的 A 记录)。

注意后面必须使用 add_vertualhost function call。

对于已经存在的域,比如前面创建了 dev,但并没有设定其 mailing list 为 dev@shopex.cn,而仍然是 dev@docs.shopex.cn 或 dev@test.shopex.cn,那么
You will want to run the bin/fix_url.py to change the domain of any existing lists.
实际上是这样做的
sh# cd /usr/local/mailman
sh# bin/withlist -l -r fix_url dev -u shopex.cn
进入一个列表来管理:
http://test.shopex.cn/mailman/amdin/users
进入可以用 site-password,也可以用 create 时发送的那个由 mailman 生成的密码,取决于你的管理策略。每个列表成员也有自己的密码,也是在订阅的时候发送的那个,可以用它登入管理自己的设置。

subscribe 可以访问 http://test1.shopex.cn/mailman/listinfo/dev 这个 URL。收到邮件后从页面 confirm 即可。

其他一些命令:
bin/list_lists
bin/rm_lists
bin/list_owners -w
bin/list_members dev

mailing list/newsgroup && google group

以前都是在论坛上发帖来讨论问题,比如在 LinuxSir 或 LinuxQuestions。现在越来越发现这种方法不太好,因为你必须维护多个帐号,每个论坛都有一个,像我这种懒人,久而久之,信息的交流就会受到影响。

因此应该考虑使用 mailing list 和 newsgroup 这样的方式来实现信息的集中管理,提高效率,便于交流。而且,如果一个问题既涉及 samba,又涉及 openldap 和 Kerberos,那么我可以同时向三个 mailing list 发帖。

比较流行的 newsgroup 是 USENET,但必须使用相应的新闻阅读器。不过目前已经有了 google group,可以不再使用新闻阅读器了。

google group 实际上包括了 newsgroup 和 mailing list 两种形式的整合。而且很多传统的 USENET newsgroup/mailing list 的内容都可以在 google group 中找到。比如我订阅了 samba@lists.samba.org 这个邮件列表,并发送了文章,那么不久就可以在"linux.samba"这个 google group 中找到,但是这个 group 却是一个 USENET newsgroup,却不包含 mailing list 功能。

因为 USENET newsgroup 存在 spam 的风险,所以我本来希望能够使用邮箱来向这样的 groups 发送内容,并使用 gmail 的 plus address 功能,这样能够更好的设置过滤规则,但结果发现对于这种 USENET newsgroup 功能不能以发送邮件的方式来操作。而新建的 google group 就都包含 mailing list 的功能,比如 sfworld, openldap 等,这些 group 可以发送邮件。通过在 group 中查看"About this group"可以发现两者的区别。

所以,如果我在其他地方订阅 mailing list(当然是比较正规的,否则就要收到大量垃圾邮件了),那么我会使用 $username+l@gmail.com 这样的用户名,而不是 $username@gmail.com。但 google group,即便有接收邮件的 list 地址,也不能使用这种 plus address 的方法!

对于 USENET 的 google group,目前我只好使用另外一个帐号,并将所有到那个帐号并与订阅的新闻组有关的邮件都转发到我现在实际使用的帐号,其他邮件一律删除,然后实际使用帐号配置可以使用这个临时帐号来发送邮件。

vsftpd xferlog LOGIN record

默认的 vsftpd 中的有如下配置:
xferlog_enable=YES
# xferlog_file=/var/log/vsftpd.log
xferlog_std_format=YES
使用 wuftpd 类型的日志记录。但却发现好像没有 LOGIN 记录!于是改成如下配置:
xferlog_enable=YES
xferlog_file=/var/log/vsftpd.log
# xferlog_std_format=YES

Mon Mar 12 17:15:22 2007 [pid 13772] [mb.shopex.cn] OK LOGIN: Client "218.80.208.72"
Mon Mar 12 17:15:33 2007 [pid 13774] [mb.shopex.cn] OK UPLOAD: Client "218.80.208.72", "/syssite/home/shop/1/template/Template
/TPL_NEWGOODS.htm", 896 bytes, 71.47Kbyte/sec

PAM stack && sufficient

关于 PAM 的 stack 有一个概念突然觉得有点模糊,比如 sufficient 控制标志,资料上的解释都是:如果 sufficient 回报认证通过,则不再调用其他模块。那么如果在 auth 中的 sufficient 模块通过,是否意味着就不用再做其他检查,比如 accout/session/password 的检查了。为了验证这一点,修改 /etc/pam.d/system-auth:
sh# vi /etc/shadow
root:**********:13524:0:99999:7::7:

sh# cat system-auth
#%PAM-1.0
# This file is auto-generated.
# User changes will be destroyed the next time authconfig is run.
auth required /lib/security/$ISA/pam_env.so
auth sufficient /lib/security/$ISA/pam_unix.so likeauth nullok
auth required /lib/security/$ISA/pam_deny.so

account required /lib/security/$ISA/pam_unix.so
account sufficient /lib/security/$ISA/pam_succeed_if.so uid < 100 quiet
account required /lib/security/$ISA/pam_permit.so

password requisite /lib/security/$ISA/pam_cracklib.so retry=3
password sufficient /lib/security/$ISA/pam_unix.so nullok use_authtok md5 shadow
password required /lib/security/$ISA/pam_deny.so

session required /lib/security/$ISA/pam_limits.so
session required /lib/security/$ISA/pam_unix.so
那么现在是不能登录的,这说明 auth pam_unix.so 的 sufficient 只影响了 auth 部分,而 account 部分还是要再检查一遍的。如果注销上面的 account pam_unix.so 一行,则可以进行登录了。

vsftpd website && sharing by users

vsftpd 为不同用户进行不同设置中,其主要目的为了可以将 apache 的不同虚拟主机赋予不同的人员进行管理,或者至少管理人员不需要使用 httpd 这个帐户来登录 ftp。那么有一个问题,就是 $htdocs_prefix/$site 的属主和权限应该如何设置(设对 $user 的 vsftpd local_root=$htdocs_prefix/$site, 即 DocumentRoot)。

我开始考虑能不能对不同的 VirtualHost/DocumentRoot 使用不同的 User/Group,于是在 httpd.conf 中进行了调整。但最终报错:
Syntax error on line 433 of /usr/local/apache2/conf/httpd.conf:
User cannot occur within "<"VirtualHost">" section
那么还是回到文件和目录权限这个思路上来。

$htdocs_prefix/$site 应该属于 httpd 用户(设 Apache 以该用户身份运行),还是应该属于 $user 这个用户?

我开始设想,如果设置属于 httpd 用户和组,因为 $user 是通过 ftp 上传文件的,而 vsftpd 这个进程是以 root 身份运行的,所以不需要另外再设置对它的系统权限,不论是否已经存在某个文件,都可以成功的进行上传(已存在则可选择是否覆盖)。而为了使 httpd 进程能够读写页面,就必须使其属主为 httpd。

这时唯一的问题是,上传后的文件的属主是 $user,而不是 httpd,所以为了使新上传的文件也能够被 httpd 正确读写,需要设置 vsftpd 的 chown_upload:
sh# vi /etc/vsftpd/vsftpd.conf
chown_uploads=YES
chown_username=httpd
但实际上这样也是不行的,因为 chown_* 只对 anonymous 用户有效,对其他用户是不行的。

所以我只能令其属于 $user 用户,并属于 httpd 组,并令 $user 也成为 httpd 的组成员,对目录增加权限:g+ws,因为如果目录拥有 g+s,则其创建的子目录也将拥有 g+s,对普通文件增加权限:g+w。这样 httpd 进程对目录和文件都具有操作能力,上传的文件也会自动属于 httpd 组。
sh# find . | xargs chmod g+w
sh# find . -type d | xargs chmod g+s

sh# vi /etc/vsftpd.conf
local_umask=002
umask 002 对普通文件及为 775,对目录即为 664。

除非在脚本中使用了 chown 这样的调用,否则这样就可以了。

对于更负责的需要,可能需要利用 cgiwrap 这样的 CGI 脚本了。在 sourceforge 上可以找到这个项目。

XML encoding UTF-8

我在 xml 文件中使用
?xml version="1.0" encoding="zh_CN.GBK"?
设定,但使用 4xml 时不行,提示不支持的字符集。于是只能使用 UTF-8
?xml version="1.0" encoding="UTF-8"?
而之前的 xml 文件使用的编码格式必须更改过来,否则仍然不能正确处理。使用 iconv 命令:
sh# iconv -f gb2312 -t utf-8 index.xml -o index2.xml
sh# export LANG=zh_CN.UTF-8
sh# vi ~/.vimrc
set encoding=utf-8
但这样做,其他不是 UTF-8 的文件又会显示乱码了。所以目前只能说在编辑时使用 set encoding=utf-8 命令来设定。

而现在在 Windows 下使用 gvim 使用上面的方法编辑是没有问题了,但我自己的 LFS 却没有对 UTF-8 的很好的支持,文件显示为乱码。不知道应该如何设定 glibc 的 locale?

星期日, 三月 18, 2007

google interview: 2nd by phone

问:TCP 三次握手协议是怎样的一个过程?
答:SYN, SYN+ACK, ACK

问:中断连接是发送什么类型的报文?
答:RST

问:traceroute 原理
...(上次问过的)

问:当不能到达目标的时候的情况?

问:有 Server1 和 Server2,之间有一个 firewall,firewall 只运行两主机的 22 端口互相连接到对方,同时只允许从 Server1 的 80 端口的访问,那么如何从 Server2 连接到 firewall 的 80 端口?
答:可以利用 ssh forwarding,使用本地转发从 Server1 的 22 端口转到 firewall 的 80 端口。

问:那么具体是什么命令来实现?
答:
sh# ssh -L 2080:firewall:80 server1
sh# ssh localhost 80


问:请问没有浏览器的时候,如何浏览页面?
答:可以通过 telnet host 80 来访问
(wget -m)

问:常用端口及 /etc/service(上次问过)

问:软、硬链接的区别?
答:符号链接即文件的内容是到目标的路径的字符串,而硬链接是在文件系统底层利用 inode 的指向来实现的。

问:kill 中止进程是使用的什么信号?
答:SIGTERM

问:这和 -9 强制中止有什么区别?
答:强制中止不会做清理工作并返回推出状态。

问:如何查看一个 pid 是属于什么进程的?
答:可以利用 pidof 命令,这个命令是 killall 的一个符号链接。

问:如何利用 kill 来中止所有的名为 javascript 的进程?
答:如果不使用 killall,则可以例如脚本:
for pid in `pidof javascript`; do
kill pid
done

问:mv * 这个命令会产生什么结果

问:/var/www/htdocs 下所有的 html 改诚 htm

find /var/www/htdocs | grep '\.html$' | xargs -i mv {} {}.l

for f in `find /var/www/htdocs | grep '\.html$'`; do
basename
awk
foo1.foo2.html
$1~$(NF-1) => name
mv /var/www/htdocs/$name ...
done

rewrite......

O(n^2)

改进算法

星期四, 三月 15, 2007

gvim setting [on windows]

"编辑"->"启动设定"
colorscheme darkblue
set tabstop=4
set number
set gfn=新宋体:h12:cGB231

"编辑"->"设定窗口"
可以查找所有的设定,例如设置字体的"set gfn=新宋体:h12:GB2312"在"10 GUI"中。

星期二, 三月 06, 2007

慎用 ifup 和 ifconfig up

因为在 RHEL4 中,默认路由总是由后面启动的那块网卡来设定的。而一般设定使用 eth0 作为内网接口,eth1 作为外网接口,如果使用 /etc/init.d/nework restart 这就没有问题,因为 eth1 总是后启动。但如果使用 ifup eth0 和 ifconfig eth0 up 这样的命令,就很可能会抹掉默认路由设置。如果 eth0 没有配置路由,还有希望登录,否则只能重启主机。

星期一, 三月 05, 2007

smbfs/cifs for fs_backup

备份的目标比较大,压缩后的档案超过两个G,使用了 smbfs 文件系统:
sh# mount
//store/homes on /mnt/host type smbfs (rw,mand)
但是执行完全备份时出现如下报错:
sh# fs_backup at_pages

gzip: stdout: File too large
tar: /mnt/host/fs_backup/at_pages/at_pages.full.20070301-010003.tgz: Wrote only 2048 of 10240 bytes
tar: Error is not recoverable: exiting now

bzip2: I/O or other error, bailing out. Possible reason follows.
bzip2: File too large
Input file = (stdin), output file = (stdout)
tar: /mnt/host/fs_backup/at_pages/at_pages.full.20070301-145957.tgz: Wrote only 8192 of 10240 bytes
tar: Error is not recoverable: exiting now
首先看看本地备份是否会有问题,改变 datadir = /var/fs_backup,重新运行完全备份,没有问题。说明限制不再 gzip/bzip2 上面,只可能是 samba 的问题。

从查找资料的情况来看,时间上就是由于 smbfs 有 2GB 的限制,使用 cifs 来代替 smbfs 即可:
sh# mount
//store/homes on /mnt/host type cifs (rw,mand)
在 /etc/fstab 做相应更改就可以了。

星期四, 三月 01, 2007

xml ts

sh$ 4xml -v index.xml
Ft.Xml.ReaderException: In file:///root/docs/shoex-sysadm/eq_room_man.xml, line 9, column 19: not well-formed (invalid token)

"<"itemizedlist mark=opencircle">"
应该写成:
"<"itemizedlist mark="opencircle"">"
因为 xml 文档的标签属性都必须使用引号!