【问题标题】:Piping smbpasswd via Java通过 Java 管道 smbpasswd
【发布时间】:2016-07-07 13:58:46
【问题描述】:

我尝试通过 Java 应用程序将密码传递给 smbpassword,这里我如何通过终端进行传递:

(echo newPassword; echo confirmPassword) | smbpasswd -a -s client1

并且输出显示该命令很好地完成了:

Added user client1

但是,我无法通过 Java 应用程序完成此操作,这里是我使用的代码:

public void run(String command, String[] prompt) {
    try {
        String[] args = new String[] {"/bin/bash","-c","echo " + rootPassword + "| sudo -S " + command};
        Process proc = new ProcessBuilder(args).start();

        if (prompt != null && prompt.length > 0) {
            for (int i = 0; i < prompt.length; i++) {
                proc.getOutputStream().write((prompt[i] + "\r\n").getBytes());
                proc.getOutputStream().flush();
            }
        }

        proc.waitFor();

        String output = "";
        String line;
        BufferedReader input = new BufferedReader(new InputStreamReader(proc.getInputStream()));
        while ((line = input.readLine()) != null) {
            System.out.println(line);
            output += line + "\n";
        }
        System.out.println(output);
        input.close();
    }
    catch (Exception ex) {
        System.out.println(null, ex.getMessage());
    }
}

我尝试像这样使用 OutputStream:

String[] pipe = { password, password };
run("smbpasswd -s -a " + username, pipe);

或像管道一样:

run("(echo " + password + "; echo " + password + ") | smbpasswd -s -a " + username, null);

但两者都不起作用,我没有输出,并且在我通过pdbedit -L 检查后似乎没有创建用户。但是,我可以通过该函数使用管道执行另一个命令,例如echo username:password | chpasswd

有什么想法吗?
提前致谢。

【问题讨论】:

    标签: java ubuntu automation samba


    【解决方案1】:

    主要的概念问题是你试图运行的shell代码:

    (echo newPassword; echo confirmPassword) | smbpasswd -a -s client1
    

    不是sudo 要求的“命令”。事实上,即使在 shell 术语中,它也不是一个“命令”。相反,它是一个管道,由一个复合命令和一个简单命令组成。 sudo 期望你提供一个简单的命令。

    但这实际上有点偏题。您的问题并不是sudo 所特有的;相反,这是一个外壳问题。在失败的情况下,您的run() 方法会调用bash 来执行以下shell 代码:

    echo rootPassword | sudo -S (echo newPassword; echo confirmPassword) | smbpasswd -a -s client1
    

    这至少有两个原因:

    1. 子shell 调用不能出现在这样的简单命令(即sudo's)参数列表中。您可以在此处放置一个命令替换 ($(echo ...)),但这不会达到您的目的,因为内部换行符将不会被保留。

    2. 即使您解决了 (1),您的第二个管道也处于错误的级别:它会将 sudo 的输出通过管道传输到 smbpasswd。这在数据方面很好,但它使 smbpasswd 在没有特权的情况下运行——sudo 不适用于它。

    您可以通过指示 sudo 通过 shell 运行代码来解决问题。您可以通过将最终命令嵌入到内部 bash -c 命令 中来做到这一点,但使用 sudo-s 选项可能会更容易(但请注意 -s 可以选择不同的 shell bash 如果你愿意的话)。如果您想在 Java 源代码中将整个操作编码为单个字符串,那么沿着这些思路可能是最通用的解决方案:

    罢工>

        String[] args = new String[] { "/bin/bash", "-c",
                "echo " + rootPassword + " | sudo -S -s '" + command + "'" };
    

        String[] args = new String[] { "/bin/bash", "-c",
                "echo " + rootPassword + " | sudo -S bash -c '" + command + "'" };
    

    然而,您的大部分问题源于您依赖 echo 将数据提供给您的命令。您应该考虑改用您的ProcessOutputStream 从您的Java 程序向其提供数据。这并不适用于非常简单的 Java 端 API,但我认为你有点自欺欺人地认为你的 API 到底有多简单。正如您所发现的,它有隐藏的陷阱,即使我上面提供的修订版仍然至少有一个:如果提交给它的命令包含单引号字符 ('),它可能会中断。

    更新:

    这是通过OutputStream 向流程提供所需数据的一种方法:

        public void run2(String command, String commandInput)
                throws IOException, InterruptedException {
            String[] args = new String[] { "sudo", "-S", "bash", "-c", command };
            Process proc = new ProcessBuilder(args).start();
            Writer toProc = new OutputStreamWriter(proc.getOutputStream());
    
            toProc.write(rootPassword, 0, rootPassword.length());
            toProc.write('\n');
            toProc.write(commandInput, 0, commandInput.length());
            toProc.close();
            proc.waitFor();
    
            BufferedReader input = new BufferedReader(new InputStreamReader(proc.getInputStream()));
            StringBuilder output = new StringBuilder();
            String line;
    
            while ((line = input.readLine()) != null) {
                output.append(line).append('\n');
            }
            System.out.println("Command output:");
            System.out.println(output.toString());
            input.close();
        }
    

    你可以在它的类中使用它

    run2("smbpasswd -a -s client1", "newPassword\nnewPassword");
    

    请特别注意,Process 的输出流通常必须在您编写完您打算写入的所有内容后关闭。如果您不这样做,您可以运行的某些命令将不会退出。

    另请注意

    对于一般情况,您必须读取每个Process 的输入流和错误流(或者如果将它们结合起来,则只读取前者),与进程本身并行。如果您没有合并流,那么您必须彼此并行读取它们。如果您正在写入进程的输出流,那么它也必须是并行的。如果您不安排通过自己的线程处理每个流,那么进程可能会卡住。

    另一方面,您并不需要一个单独的线程来运行Process 本身——毕竟,它代表了一个完整的独立进程。只需推迟等待它,直到您关闭其输出流并读取其输入和错误流的结尾。

    【讨论】:

    • 感谢您的回答和解释!我尝试了你提供的参数数组,但管道不起作用,InputStream 没有向我提供输出。关于 OutputStream,我更新了我的问题,也许你可以给我一个正确的代码示例?
    • @lumine,-s 选项是一个糟糕的选择,抱歉,虽然总体思路是合理的。我已经更新了我的答案的那部分。我还提供了一个示例,说明如何通过将数据直接从 Java 提供给外部进程来简化事情。这仍然不是通用解决方案(请参阅我添加的关于并行性的 cmets),但它应该适用于产生很少输出的情况。
    猜你喜欢
    • 2010-09-05
    • 1970-01-01
    • 1970-01-01
    • 2011-02-09
    • 2016-07-25
    • 2014-02-08
    • 2021-09-14
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多