2009年5月7日星期四

继续折腾:通过Web方式修改Linux用户密码的PHP脚本

搞了一晚上,现在有点头晕,可能写的不是很清楚,有些遗漏,有问题请留言,谢谢。

昨天协会到了一台服务器,在我的强烈要求下跑了Linux,然后有人提出来可不可以在上面跑个Apache+PHP给小朋友们做主页玩,我觉得这个想法不错于是开搞。最开始是计划用proftpd进行文件上传的,但是配置了半天,发现使用mysql后端的proftpd性能不是一般的差- -||,进行个ls都要等5秒以上,Google无果,以前也没有接触过这玩意儿,所以决定换回相对熟悉的vsftpd,外加apache的userdir模块,然后使用系统帐号进行控制(我知道vsftpd也能用mysql做后端,但是一样没用过)。
但是只用系统帐号就存在一个密码更改的问题,总不能人家每次想改个密码都要告诉我手工改吧(不想给他们开ssh权限)。于是,下面这个玩意儿出现了。
首先是代码:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="zh_cn" lang="zh_cn">
<head>
<title>修改登录密码</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
<?php
$u = $_POST['u'];
$o = $_POST['o'];
$p = $_POST['p'];
$r = $_POST['r'];
$error = 'no error';
if ($u){
echo '<pre>';
if($p!=$r){
echo 'ERROR: Password not match, please check your input.';
//}else if($u!=$_SERVER['PHP_AUTH_USER']){
// echo 'ERROR: Unable to change other\'s password.';
}else if(!$p){
echo 'ERROR: New Password is empty!';
}else if(strpbrk($p,"\r\n")||strpbrk($u,"\r\n")){
echo 'ERROR: Account name and password should not contain \r or \n!';
}else{
echo "Tying to change password for {$u}...\n";
if(!pam_auth($u,$o,$error)){
echo "ERROR: $error\n";
}else{
$descriptorspec = array(
0 => array("pipe", "r"), // stdin is a pipe that the child will read from
1 => array("pipe", "w"), // stdout is a pipe that the child will write to
2 => array("pipe", "w") // stderr is a file to write to
);

$cwd = NULL;
$env = NULL;

$process = proc_open('sudo /usr/sbin/chpasswd', $descriptorspec, $pipes, $cwd, $env);
if (is_resource($process)) {
fprintf($pipes[0],"%s:%s\n",$u,$p);
fclose($pipes[0]);

echo stream_get_contents($pipes[1]),"\n";
fclose($pipes[1]);

echo stream_get_contents($pipes[2]),"\n";
fclose($pipes[2]);

$return_value = proc_close($process);

echo "Command returned $return_value";
}
}
}
echo '</pre>';
}else{
$u=$_SERVER['PHP_AUTH_USER'];
}
?>
<form method=POST><table>
<tr><td colspan=2 align='center'>修改用户口令</td></tr>
<tr><td>用户名: </td><td><input type=text name='u' value='<? echo $u; ?>'/></td></tr>
<tr><td>原密码: </td><td><input type=password name='o'/></td></tr>
<tr><td>新密码: </td><td><input type=password name='p'/></td></tr>
<tr><td>重复密码:</td><td><input type=password name='r'/></td></tr>
<tr><td colspan=2 align='center'><input type=submit value='确认'>  <input type=reset value='取消'></td></tr>
</table></form>
</body>
</html>

然后讲一下如何配置:
首先,这里用到了一个php扩展:pam,我的服务器是ubuntu的,装这个东西时也废了不少功夫,首先apt-get install php-pear 然后还要装几个dev库(php5-dev libpam-dev)然后就可以用pear pam下载编译这个叫pam的扩展了。
装好这个还要设置:新建/etc/pam.d/php,内容从login文件里面复制就可以了,或者你可以试试他说明文档中提到的那个:

# /etc/pam.d/php
#
# note: both an auth and account entry are required

auth sufficient /lib/security/pam_pwdb.so shadow nodelay
account sufficient /lib/security/pam_pwdb.so

这个我试过,好像不能用,不知道是不是版本的问题。
然后麻烦的事情来了,要设置其他用户的密码,必然需要root权限,假如我们给予了apache的用户某些特权,必然会对整个系统的安全造成影响,尤其是我现在还允许用户上传自己的PHP,而PHP的disable_functions只能全局设置,所以我觉得从根本上吧用户和特权基本分类:使用多个Apache实例,每个实例使用不同的php配置。
设置参考这个帖子:http://blog.datajelly.com/company/blog/46-multiple-apache-instances-in-ubuntu.html
我是这样设置的:新的apache只监听SSL的443端口,使用web_admin这个低权账户登录系统(而用户使用的旧apache使用www-data),最小化配置(关闭所有无用的modules),关闭php的安全设置。在独立Php配置时使用了PhpIniDir这个httpd.conf的参数,用过win32下的apache+php的应该熟悉。

使用visudo修改/etc/sudoer这个文件,在文件的最后添加:
web_admin ALL=NOPASSWD: /usr/sbin/chpasswd

注意这里一定要用visudo这个东西修改,不要直接打开!
这样就赋予了web_admin这个用户使用root权限运行/usr/sbin/chpasswd的权限了,比直接设置sid之类的方法要安全一些

这样一来,整个系统就大概可以投入使用了。

0 人次吐槽:

发表评论