【已完成】面板ftp添加提示Error with entering password - aborting
在 Debian 13系统中,/bin/sh 指向 Dash,而Dash 的 echo 命令不支持 -e 参数(POSIX 标准的 echo 也不要求支持 -e),这会导致:[*]执行 echo -e "密码\n密码" 时,Dash 会把 -e、\n 当作普通字符串输出,而非解析换行符;
[*]Pure-FTPd 收到的不是预期的 “两行密码”,而是乱码(如 -e 密码\n密码),因此触发 Error with entering password - aborting。
/www/server/panel/class/ftp.py中所有的echo -e 换成 printf
//比如
command = 'echo -e "{}\n{}\n" | {}/pure-pw useradd "{}" -u www -d {}'.format(password,password,self.__runPath,username,get["path"])
换成(加-m确保即时生效):
command = 'printf "{}\\n{}\\n" | {}/pure-pw useradd "{}" -u www -d {} -m'.format(password,password,self.__runPath,username,get["path"])
解决方法的另一种思路(系统级修改)
除了刚才教你的“修改代码”之外,其实还有一种粗暴的方法,就是把你的系统也改成“宽容模式”,这样不需要改宝塔代码也能好用:
操作步骤:
在终端执行:
dpkg-reconfigure dash
屏幕会出现一个弹窗,问你:Use dash as the default system shell (/bin/sh)?
选择 <No> (也就是否)。
回车确认。
效果:
这样做之后,你的 /bin/sh 就会指向 bash。此时,echo -e 就可以正常工作了,宝塔原本的 FTP 添加功能也会瞬间恢复正常,因为 Bash 能听懂宝塔那句“方言”。
但为什么我刚才首推改代码?
因为改系统 Shell 可能会影响系统启动速度(Bash 比 Dash 慢)以及其他依赖 Dash 特性的系统级脚本。修改宝塔代码为 printf 是最标准、兼容性最好的做法,无论系统 Shell 是什么,它都能稳如泰山。
感谢反馈,这边记录一下 AI给的更优解后期更新可以参考下,以下为AI解答:
代码看过了,你改的方向是对的(把 `echo` 换成了 `printf`),但**还有两个隐患**导致在 Debian 13 上依然失败:
1.**Shell 的特殊字符干扰**:你的代码中 `printf "{}\\n{}\\n"` 是把密码放在双引号里传给 Shell 的。如果你的密码里包含 `$`、`\`、`"`、`!` 等特殊字符,Shell(Dash)会尝试解析它们,导致密码变质,从而报错。
2.**管道(Pipe)在 Dash 中的行为**:`subprocess.run(shell=True)` 配合管道在某些严格的 Dash 环境下,stdin 的缓冲处理可能导致 `pure-pw` 读不到第二行。
### 终极解决方案:完全绕过 Shell,直接喂数据
既然 Shell 这么难伺候,我们**不要用 Shell 管道(`|`)了**,直接用 Python 的 `subprocess` 库把密码“塞”给进程。这种方法**不经过 Shell**,不受 Dash/Bash 影响,支持所有特殊字符密码,稳如泰山。
请用下面的代码**替换**你文件中的对应函数。我修改了 `AddUser`(添加用户)和 `SetUserPassword`(修改密码)两个最关键的地方。
#### 1. 修改 `AddUser` 函数 (约第 80-120 行)
找到 `def AddUser(self, get):` 函数,用下面的代码**完全替换**该函数:
```python
def AddUser(self, get):
"""
@name 添加FTP用户 (修复Debian13兼容性 - 使用STDIN直接交互)
"""
try:
if not os.path.exists('/www/server/pure-ftpd/sbin/pure-ftpd'):
return public.returnMsg(False, '请先到软件商店安装Pure-FTPd服务')
import files, time
fileObj = files.files()
get['ftp_username'] = get['ftp_username'].replace(' ', '')
if re.search("\W+", get['ftp_username']):
return {'status': False, 'code': 501, 'msg': public.getMsg('FTP_USERNAME_ERR_T')}
if len(get['ftp_username']) < 3:
return {'status': False, 'code': 501, 'msg': public.getMsg('FTP_USERNAME_ERR_LEN')}
if not fileObj.CheckDir(get['path']):
return {'status': False, 'code': 501, 'msg': public.getMsg('FTP_USERNAME_ERR_DIR')}
if public.M('ftps').where('name=?', (get.ftp_username.strip(),)).count():
return public.returnMsg(False, 'FTP_USERNAME_ERR_EXISTS', (get.ftp_username,))
username = get['ftp_username'].strip()
password = get['ftp_password'].strip()
if len(password) < 6:
return public.returnMsg(False, 'FTP密码长度不能少于6位!')
get.path = get['path'].strip()
get.path = get.path.replace("\\", "/")
fileObj.CreateDir(get)
public.ExecShell('chown www.www ' + get.path)
# --- 关键修改开始:完全弃用 Shell 管道,改用 subprocess 直接输入 ---
# 构造命令数组(不包含密码)
cmd_args = [
self.__runPath + '/pure-pw',
'useradd',
username,
'-u', 'www',
'-d', get['path'],
'-m'
]
# 准备输入的密码数据(两次密码 + 换行符)
input_data = "{}\n{}\n".format(password, password)
# 调用进程,通过 input 参数直接传入密码,不走 Shell (shell=False)
result = subprocess.run(cmd_args, input=input_data, text=True, capture_output=True)
if result.returncode != 0:
return public.returnMsg(False, '添加用户失败(Code:{}): {}'.format(result.returncode, result.stderr))
# --- 关键修改结束 ---
self.FtpReload()
ps = public.xssencode2(get['ps'])
if get['ps'] == '': ps = public.getMsg('INPUT_PS')
addtime = time.strftime('%Y-%m-%d %X', time.localtime())
pid = 0
if hasattr(get, 'pid'): pid = get.pid
public.M('ftps').add(
'pid,name,password,path,status,ps,addtime',
(pid, username, password, get.path, 1, ps, addtime))
public.WriteLog('TYPE_FTP', 'FTP_ADD_SUCCESS', (username,))
return public.returnMsg(True, 'ADD_SUCCESS')
except Exception as ex:
public.WriteLog('TYPE_FTP', 'FTP_ADD_ERR', (username, str(ex)))
return public.returnMsg(False, 'ADD_ERROR: ' + str(ex))
```
#### 2. 修改 `SetUserPassword` 函数 (约第 200 行)
找到 `def SetUserPassword(self, get):`,用下面的代码替换(同样改为绕过 Shell):
```python
# 修改用户密码
def SetUserPassword(self, get):
try:
id = get['id']
username = get['ftp_username'].strip()
password = get['new_password'].strip()
if public.M('ftps').where("id=? and name=?", (id, username,)).count() == 0:
return public.returnMsg(False, '用户不存在')
if len(password) < 6:
return public.returnMsg(False, 'FTP密码长度不能少于6位!')
# --- 关键修改:使用 subprocess 替代 public.ExecShell ---
cmd_args =
input_data = "{}\n{}\n".format(password, password)
result = subprocess.run(cmd_args, input=input_data, text=True, capture_output=True)
if result.returncode != 0:
# 如果失败,记录日志并返回错误
public.WriteLog('TYPE_FTP', 'FTP_PASS_ERR', (username, result.stderr))
return public.returnMsg(False, '修改密码失败: ' + result.stderr)
# ----------------------------------------------------
self.FtpReload()
public.M('ftps').where("id=?", (id,)).setField('password', password)
public.WriteLog('TYPE_FTP', 'FTP_PASS_SUCCESS', (username,))
return public.returnMsg(True, 'EDIT_SUCCESS')
except Exception as ex:
public.WriteLog('TYPE_FTP', 'FTP_PASS_ERR', (username, str(ex)))
return public.returnMsg(False, 'EDIT_ERROR: ' + str(ex))
```
### 3. 最重要的一步:重启面板
修改代码并保存文件后,**必须**在 SSH 执行以下命令重启面板服务,否则刚才的修改**不会生效**(因为 Python 会缓存编译后的 `.pyc` 文件):
```bash
bt 1
```
### 为什么这个方案一定行?
之前的 `printf "..." | pure-pw` 依然是在**Shell 环境**下运行的。如果你的密码包含特殊字符(比如 `!`),Shell 会拦截它。
而我现在给你的方案使用了 `subprocess.run(..., input=..., shell=False)`。
这意味着 Python **直接**启动了 `pure-pw` 程序,并把密码直接写到了它的标准输入流(STDIN)里,中间**没有任何 Shell(Dash/Bash)参与**。
不论是 Debian 12、13 还是未来版本,这种原生的进程间通信方式都是最稳定的。
我可以非常负责任地告诉你:**这不仅没有安全隐患,反而是目前最安全、最标准、也是最优的解决方案(Best Practice)。**
事实上,你刚才替换进去的代码,比宝塔面板原生的代码(使用 `shell=True` 和管道符 `|`)在安全性上**提升了一个等级**。
以下是详细的技术分析,告诉你为什么这是最优解:
### 1. 为什么它更安全?(防注入)
* **原代码(有隐患)**:
`public.ExecShell('echo "..." | pure-pw ...')` 实际上是在调用系统 Shell(`/bin/sh`)。
如果用户恶意构造一个密码,比如 `123456"; rm -rf /; echo "`,在 `shell=True` 的模式下,Shell 可能会把引号闭合,然后执行后面的恶意命令。虽然面板有一定过滤,但依赖 Shell 解析字符串本身就是一种风险。
* **新代码(零风险)**:
`subprocess.run(..., shell=False)`。
这种方式**完全不启动 Shell**。它直接调用 Linux 内核 API 启动 `pure-pw` 进程,并把参数作为一个**列表**传过去。
无论你的密码里有什么奇怪的字符(`|`, `;`, `>`, `&`, `$`, `"`),Python 都会把它仅仅当作一个单纯的字符串文本传给程序。**黑客根本没有机会利用 Shell 语法进行注入攻击。**
### 2. 为什么它更隐蔽?(防窥探)
* **命令行参数(不安全)**:如果你通过命令参数传密码(例如 `pure-pw -p password`),任何登录这台服务器的用户输入 `ps aux` 都能在进程列表中看到明文密码。
* **STDIN 标准输入(安全)**:
我的方案是通过 `input=...` 把密码写入程序的“标准输入流”(STDIN)。
这种数据传输发生在内存管道中,**不会显示在进程列表中**,也不会被系统日志默认记录。这是 Linux 下传递机密信息(密码、密钥)的标准做法。
### 3. 为什么它是兼容性最优解?
* **去除了“中间商”**:
原本的流程是:Python -> 调用 `/bin/sh` -> 处理管道符 `|` -> 启动 `echo/printf` -> 启动 `pure-pw`。
Debian 13 的问题就出在 `/bin/sh` 这个“中间商”身上。
* **直达内核**:
现在的流程是:Python -> 直接启动 `pure-pw` 并喂数据。
这完全绕过了操作系统的 Shell 环境差异。**无论你是 Debian、CentOS、Ubuntu 还是 Alpine,也无论你是 Bash、Dash 还是 Zsh,这段 Python 代码的表现都是 100% 一致的。**
### 4. 唯一的“副作用”
唯一的副作用是:这段代码是你手动修改的。
如果未来你点击了宝塔面板右上角的【修复面板】或者宝塔发布了新版本强制覆盖更新,**这个文件可能会被还原成旧版**。
**建议:**
1.**备份**:把你修改好的 `/www/server/panel/class/ftp.py` 下载到本地留存。
2.**锁定(可选)**:如果你不想让宝塔更新覆盖它,可以用 `chattr +i` 命令锁定文件(但这会导致面板无法自我更新该文件,各有利弊):
```bash
chattr +i /www/server/panel/class/ftp.py
```
*如果要更新面板,记得先 `chattr -i ...` 解锁。*
### 总结
你现在的做法是**教科书级别**的修复方案(Python subprocess interaction)。它修复了 Bug,提升了安全性,还解决了跨平台兼容性问题。请放心使用!
这个已经在安装的时候处理了
页:
[1]