Sunday, May 30, 2010

帐号

1. 问题的来源
一直以来, 都有一种错误的认为: 使用useradd增加帐号和配置好ssh-key就可以使用此帐号登陆 .
实现上不如此. 这是需要usePAM的支持. PAM日后再细细学习, 现在先学习学习一般认证

于是, 把login和su命令的代码翻出来看看. 它们都使用如下的方式:

login(不扯PAM)的代码:
 798     pp = getpass(_("Password: "));
 799
 800 #  ifdef CRYPTOCARD
 801     if (strncmp(pp, "CRYPTO", 6) == 0) {
 802         if (pwd && cryptocard()) break;
 803     }
 804 #  endif /* CRYPTOCARD */
 805
 806     p = crypt(pp, salt);
 807     setpriority(PRIO_PROCESS, 0, 0);
.......
 835     if (pwd && !strcmp(p, pwd->pw_passwd))
 836       break;
 837
 838     printf(_("Login incorrect\n"));

su的代码:
265   unencrypted = getpass (_("Password:"));
266   if (!unencrypted)
267     {
268       error (0, 0, _("getpass: cannot open /dev/tty"));
269       return false;
270     }
271   encrypted = crypt (unencrypted, correct);
272   memset (unencrypted, 0, strlen (unencrypted));
273   return STREQ (encrypted, correct);
274 }

都是:
1. 使用了crypt函数
2. 最后都是字符串比较
3. 需要/etc/shadow里的加密后字符串
* 比红色部分可以看出getpass是在stdin被定重向后会报错(hard wire型的程序), 因为它使用了不回显功能(只有tty才有, pipe没有)

在python官网里看到可爱的python代码是这样的:
crypt.crypt(cleartext, cryptedpasswd) == cryptedpasswd

* 可以知道crypt函数的第二个参数可以是salt, 也可以不是salt, 而是加密后的字符串
* 自来http://docs.python.org/library/crypt.html?highlight=crypt#crypt.crypt



2. Lock 与 Unlock 是怎么一个概念?

在man 5 shadow得到的信息:

If the password field contains some string that is not valid result of crypt(3), for instance ! or *, the user will not be able to use a unix password to log in, subject to pam(7).

从上面的C代码和crypt函数的特性可以猜测: Lock无疑就是让方程两边永远不相等, 代码没有其它测试的必要!

具体到ssh的实例, 当使用一指定用户登陆ssh server时, server并不去分别Lock与不Lock的帐号,
虽然没有看过sshd的代码, 但是从表现上: 不会马上打印: Lock user或者马上disconnect, 如使用全世界都公认的halt默认帐号也是提示输入密码:
$ ssh halt@localhost
halt@localhost's password:

3. 其它
su切换用户
FAIL : 以为切换什么用户都是输入root密码
大概看了一把su的代码, 才知道不是这样的~~~~
知道了下面的真相:
1. 需要输入切换到用户的密码
2. restricted shell : 凡不在/etc/shells里登陆的shell都是受限shell. 为了安全的

nologin
平时nologin默认打印信息是: This account is currently not available.
你也是可以修改的: /etc/nologin.txt, 例如下面的例子:

$ sudo su rpc
我顶~ 乱来~ 去问你妈要密码, 不然见一次骂一次
 * 这个例子有点geek~





Friday, May 28, 2010

虚假终端: pseudo-terminal

之前只听前pseudo terminal, 但是没有理会. 前段时间在写脚本时受点一点限制 

1. 为什么使用subprocess无法与子进程多次通信?
2. 像su这种为什么使用subprocess无法调用? ( 这种软件有一个词来描述: hard wire , 表示需要terminal, 代码中使用了istty函数检测到 )
3. vim这种软件的交互是怎么实现的?

subprocess的communicate函数是代码是这样的:

            if self.stdin:
                if input:
                    self.stdin.write(input)
                self.stdin.close()

这里为什么需要self.stdin.close()呢? 这就是subprocess无法与子进程多次通信的地方.
在不明白为什么需要close的情况下, 自己写的一段fork功能的代码, 結果: 当父进程在读子进程的stdout时被block了. 其实, 这时父进程和子进程都是block的, 这被称为"死锁". 这就是为什么subprocess里需要close

那么, 我的需求:
1. 在调用外部进程的情况下, 如何多次父-子进程通信?
2. 如何调用hard wire的程序?

种种问题的浮现让sysadmin岗位的本人不爽. 至少不方便工作....

例子:
1. passwd的调用
passwd是一典型的hard wire程序, 下面的方式调用是不行的:
$ echo "new password" | passwd
比较有意思的是passwd在不同的系统或者是distro下都是不同的.
 * freeBSD下有参数控制(具体的忘了, 也没有环境回顾), 成为batch mode
 * redhat系使用"--stdin"控制
 * ubuntu下没有batch mode参数

2. su -c "my command"的调用
使用在系统上使用su ( 我不得不说, 没有sudo使用~~~), 这将是一个很大的麻烦~~

后知后觉, 发现了pseudo terminal这一概念. 它的应用很广泛, 比如提供network login的sshd就使用到此概念


在朋友(mrluanma (AT) gmail.com)的帮忙下, 找到pexpect程序(http://www.noah.org/wiki/Pexpect). 提供了一个很好的学习模板.

刚接触pseudo terminal很头大的, 还好APUE一书中有这部分和关于terminal I/O的资料, 下面是一个很清晰的图:


本人写了两个简单的例子:
1. 使用pty.fork()函数产生子进程, 子进程与父进程的controlling terminal是不同的.
帐号与密码都是svn

2. 使用pty实现调用passwd这种hard wire外部程序
帐号与密码都是svn





Monday, May 24, 2010

记一次开源软件原理追查

有这种dig行为, 主要是本人想写完一篇笔记, 是关于xxx.pid文件与锁的文章。
在实际和自己了解的情况中, 掌握的东西不够多, 所以就dig下去了。

在shell中, 如果要得到文件锁,在debian系的distro下可以使用这个包:  lockfile-progs

ubuntu下已经预装
http://packages.qa.debian.org/l/lockfile-progs.html

因为一直对lock file 和 file lock这两个名词纠结不清, 所以就下载源代码(还好, 很简短)来看

发现使用了liblockfile库, 视线被转到这一个library上, 首先想到的是找这个库的文档

dpkg -l|grep lockfile
得到:
liblockfile1

dpkg -L liblockfile1
只有一份man手册,是关于随liblockfile1包发布的dotlockfile程序的说明。

嗯, 至少比没有好吧.....man之

不过很有意思的是从这份手册里看到一点线索:
The above mentioned lockfile_create(3) manpage is present in the liblockfile-dev package.

呵呵, lockfile_create 这个API的手册在dev包里, 马上apt-get安装

lockfile_create API手册里有一节叫ALGORITHM的。 其实这一节已经说明了原理和流程了(从man手册里的文字转成流程还是需要很细心地阅读)
我对其它的 "A check is made to see if the existing lockfile is a valid one" 不是很明白, 所以找了lockfile_check函数的代码看, 这里就不贴代码了(还是wordpress写blog好呀~~)
代码可以出下面的地址得到:
 * http://liblockfile.sourcearchive.com/documentation/1.06.1/lockfile_8c-source.html

lockfile_check函数的判断有两个:
1. lock file中保存PID号代表的进程是否还存在, 通过kill函数得到。 (用法与xxx.pid文件类似, 关于xxx.pid用法, 本人别起笔记)
2. 如果lock file 中保存的pid号为0, 则与5分钟为lock file生命期。


xxx.pid文件与lock file

linux下, xxx.pid这类文件还是很常见的, 特别是在/var/run目录下, APUE一书都是推介在这里创建pid文件。
pid文件到底有什么作用呢? 用法是如何的呢?种种疑问让我产生了dig的冲动。

先看看实现生活中碰到的:

1. init.d脚本使用到pid文件

/etc/init.d目录下存放了众多daemon程序的控制脚本,这类脚本一般有几个参数:
  1. start
  2. retstart
  3. stop
  4. 强大的专业服务端还有gracestop之类的参数
这类脚本的这些参数功能的实现是使用到了linux的signal机制, 无疑需要daemon进程的pid

2. lock

lock主要是协调, 操作atomic时使用的一种方式。
举个实际中的例子:
系统需要一个定时任务, 要求每分钟对系统N多状态收集(如硬盘,CPU, 网络等等), 收集过程中不断有日志产生。

这个例子会有两个问题:
  1. 运行间隔为每分钟, 可能会定时任务某次运行时长长于1分钟。
  2. 如果出现1的情况会对日志产生影响
lock主要是用于证实有进程在处理。

linux系统本身有锁的机制, 如flock和fcntl函数。 这种锁称为file lock, 文件锁
linux下的文件锁可以对一个文件的一个区域进行锁定。

现在, 本笔记不想扯上这种强大功能, 只是谈论上面举的例子: 怎么让进程得到一个結果:能否处理

完成这需求, 只需要一个标志, 这个标志需要进程在得到时是atomic性的。

可以通过两种方法得这种标志:
  1. 指定文件已经存在, 表示有进程在处理. 这种方式称为lock file
  2. 使用系统本身的file lock机制, 如果open的file descriptor有锁, 表示有进程在处理

使用linux的flock函数得到file lock, 这里有一个python的例子:
http://svn.lvscar.info/jessinio_repos/flock_example.py
  * 帐号与密码都是svn

lock file 和 file lock 的区别

lock file是利用系统本身的open, link, stat这样的函数, 实现文件创建的atomic性. 成功被创建的文件作为atomic的标志

file lock是利用系统本身的fcntl或者flock函数对某一的文件进行锁定, 将能否成功锁定文件作为atomic的标志

无论是lock file 还是 file lock都是需要文件这一角色的. 但是这一文件起的作用不同.

本文主要是想扯lock file与xxx.pid文件的关系. 不想扯file locklock file

仅仅需要可以标志行为的atomic性的话, lock file是最好的方法:
1. NFS上可以使用(flock函数在旧版本的NFS上不起作用)
2. shell脚本可用. 因为是shell脚本是CLI, 时刻都是新的进程在处理, 不方便使用flock(这样需要一个daemon在保持file lock的存在
 * 关于 lock file可以移步: http://en.wikipedia.org/wiki/File_locking#Lock_files

使用lock file需要解决的问题

当使用lock file作为atomic的标志时, 有一种情况是需要解决的: 持锁者是否还存在, 例如:

当一个进程成功创建了lock file后, 在不明异常的情况终止了, 后面的进程怎么判断刚才被创建的lock file是被进程使用的? ( 使用file lock就没有这种问题, 得到锁的进程终止后锁会被释放, 新的进程可以得到)

具体处理方式可以参考liblockfile的原代码:
 * http://liblockfile.sourcearchive.com/documentation/1.06.1/lockfile_8c-source.html

在它的lockfile_check函数里, 使用到了kill函数. 这个kill函数需要的pid就是xxx.pid文件的pid

因此:

xxx.pid有两种作用:
1. 方便daemon被控制(通过signal)
2. 可以用于实现lock file

利用了1不一定利用2, 但是作用了2一定需要利用1

另外

除了利用kill给指定的pid发signal的方式去判断某进程是否存在的方式后, 还可以使用如下URL提到的方法:
http://stackoverflow.com/questions/2735926/how-to-capture-pid-of-a-linux-daemon-run-from-init-d

利用/proc/NamePID/exe得到的字符串是否为先前在进行的进程名. 这种方法需要知道进程的名称.

疑问

是否会出现pid号的进程存在, 但不是自己期望的进程呢?
查阅了APUE也没有明确, 只知道是有一定的算法去reuse 进程号的.

在这方面, 还是flock方便, 不过此函数不是所有地方都可以使用的, 纠结`~~





Thursday, May 20, 2010

exec*族函数的好玩参数

一直以来, 感觉exec*族函数都是比较乱的。 查查手册就可以知道分来如下几类:
l: stands for list
v: stands for vector , 类似于main中的argv, 表示数组。
p: path
e: environment

本blog不是想写上面的内容的。

本人是对execl*都第二个参数, execv*第二个参数的第一个成员感兴趣。

很早之前就对这个参数不懂。 例如:

os.execlp("sleep", 'sleep', '10')
当同于:
os.execvp("sleep", ['sleep', '10])

看上去感觉不是很好, 第二个参数用来干嘛的? 为什么要重复写出来?

主要是在不知道那个参数是干什么的情况下, 写得和第一个参数相同, 所以就有这种重复的感觉.

起什么作用的呢? 看下面吧:

jessinio@jessinio-laptop:/proc/7345$ sleep 10 &
[1] 7594
jessinio@jessinio-laptop:/proc/7345$ ps x|grep sleep
 7594 pts/10   S      0:00 sleep 10
jessinio@jessinio-laptop:/proc/7345$ /bin/sleep 10 &
[2] 7597
jessinio@jessinio-laptop:/proc/7345$ ps x|grep sleep
 7597 pts/10   S      0:00 /bin/sleep 10

* 相同颜色部分的关系: 相同的
* 此字符串来自/proc/*/cmdline 文件内的信息

exec*族的第二个参数(对于v类是第二个参数的第一个成员)是起到显示的作用, 如:

import os
import sys


if __name__ == "__main__":
    pid = os.fork()
    if pid == 0:
        child = ['i love linux', '10']
        os.execv('/bin/sleep', child)
    else:
        print "%s" % pid
        os._exit(0)

运行得到:
jessinio@jessinio-laptop:/tmp$ python exec_bin.py
7892
jessinio@jessinio-laptop:/tmp$ ps aux|grep 7892
jessinio  7892  0.0  0.0   2956   628 pts/9    S    21:55   0:00 i love linux 10

有点与众不同的玩法~~

Monday, May 17, 2010

进程成为daemonize需要fork两次吗?

成为一个daemonize, 其实网上有很多例子, 也是可以使用的, 正如:
http://code.activestate.com/recipes/278731-creating-a-daemon-the-python-way/
里给出的例子。

但是, 我无法明白为什么要fork两次!, 下面是我的测试:

jessinio@jessinio-laptop:/tmp$ ps axj|head -n 1
 PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
jessinio@jessinio-laptop:/tmp$ ps axj|grep apache
   1  1820  1820  1820 ?           -1 Ss       0   0:10
/usr/sbin/apache2 -k start
 1820 16100  1820  1820 ?           -1 S     1000   0:00
/usr/sbin/apache2 -k start
 1820 16101  1820  1820 ?           -1 Sl    1000   0:00
/usr/sbin/apache2 -k start
 1820 16129  1820  1820 ?           -1 Sl    1000   0:00
/usr/sbin/apache2 -k start


可以中从得到这样的信息:
daemonize进程的PPID==1, PID==PGID==SID, 没有tty(为?)

如果这就是daemonize进程的要求的话, 请看我自己写的python代码:

if __name__ == "__main__":
   pid = os.fork()
   # child process
   if pid == 0:
       os.setsid()
       time.sleep(10)
   else:
       print 'parent %s, child %s' % (os.getpid(), pid)
       os._exit(0)

其中只是调用了一次fork函数。下面是它的运行結果:

jessinio@jessinio-laptop:/tmp$ ps axj|head -n 1
 PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
jessinio@jessinio-laptop:/tmp$ python process.py
parent 26117, child 26118
jessinio@jessinio-laptop:/tmp$ ps axj|grep process
   1 26118 26118 26118 ?           -1 Ss    1000   0:00 python process.py


可以看到, PPID==1, PID==PGID==SID, 没有tty

这是否可以认为: 只fork一次, 然后调用setsid就可以成为daemonize进程了呢?

在众多不解的情况下, 决定好好看看圣经: APUE 一书.

也很高兴, 从中找到了一点信息:

under system v-based systems, some people recommend calling fork again at this point and having the parent terminate, the second child continues as the daemon, This guarantees that the daemon is not a session leader , which prevents it from acquiring a controlling terminal under the system v rules. alternatively, to avoid acquiring a controlling terminal, be sure to specify O_NOCTTY whenever opening a terminal device.

fork第二次是防止被在打开一个terminal device时被系统分配到controlling terminal

只要不乱open(或者使用O_NOCTTY是不需要fork第二次的.

话又说回来, linux是怎么分配controlling terminal的呢?

APUE在这里没有提到linux, 只是提到了BSD和system-V的不同. 搜了一下, linux和BSD是一样的, 使用ioctl得到controlling terminal, URL: http://linux.die.net/man/4/tty_ioctl
如下引用:

TIOCSCTTY int
arg Make the given tty the controlling tty of the current process.