blob: 4dd43406f7c27320a212bd4605ee2cee7b6786db [file] [log] [blame] [edit]
// Copyright 2022 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "debugd/src/lp_tools.h"
#include <signal.h>
#include <unistd.h>
#include <string>
#include <vector>
#include <base/files/file_util.h>
#include <base/functional/callback_helpers.h>
#include <base/logging.h>
#include <base/strings/string_split.h>
#include "debugd/src/helper_utils.h"
namespace debugd {
namespace {
constexpr char kLpadminCommand[] = "/usr/sbin/lpadmin";
constexpr char kLpadminSeccompPolicy[] =
"/usr/share/policy/lpadmin-seccomp.policy";
constexpr char kLpstatCommand[] = "/usr/bin/lpstat";
constexpr char kLpstatSeccompPolicy[] =
"/usr/share/policy/lpstat-seccomp.policy";
constexpr char kTestPPDCommand[] = "/usr/bin/cupstestppd";
constexpr char kTestPPDSeccompPolicy[] =
"/usr/share/policy/cupstestppd-seccomp.policy";
constexpr char kUriHelperBasename[] = "cups_uri_helper";
constexpr char kUriHelperSeccompPolicy[] =
"/usr/share/policy/cups-uri-helper.policy";
constexpr char kLpadminUser[] = "lpadmin";
constexpr char kLpadminGroup[] = "lpadmin";
constexpr char kLpGroup[] = "lp";
} // namespace
// Returns the exit code for the executed process.
int LpToolsImpl::RunAsUser(const std::string& user,
const std::string& group,
const std::string& command,
const std::string& seccomp_policy,
const ProcessWithOutput::ArgList& arg_list,
const std::vector<uint8_t>* std_input,
bool inherit_usergroups,
const base::EnvironmentMap& env,
std::string* out) const {
ProcessWithOutput process;
process.set_separate_stderr(true);
process.SandboxAs(user, group);
if (!seccomp_policy.empty())
process.SetSeccompFilterPolicyFile(seccomp_policy);
if (inherit_usergroups)
process.InheritUsergroups();
if (!env.empty())
process.SetEnvironmentVariables(env);
if (!process.Init())
return ProcessWithOutput::kRunError;
process.AddArg(command);
for (const std::string& arg : arg_list) {
process.AddArg(arg);
}
// Starts a process, writes data from the buffer to its standard input and
// waits for the process to finish.
int result = ProcessWithOutput::kRunError;
process.RedirectUsingPipe(STDIN_FILENO, true);
if (process.Start()) {
// Ignore SIGPIPE.
const struct sigaction kSigIgn = {.sa_handler = SIG_IGN,
.sa_flags = SA_RESTART};
struct sigaction old_sa;
if (sigaction(SIGPIPE, &kSigIgn, &old_sa)) {
PLOG(ERROR) << "sigaction failed";
return 1;
}
// Restore the old signal handler at the end of the scope.
const base::ScopedClosureRunner kRestoreSignal(base::BindOnce(
[](const struct sigaction& sa) {
if (sigaction(SIGPIPE, &sa, nullptr)) {
PLOG(ERROR) << "sigaction failed";
}
},
old_sa));
int stdin_fd = process.GetPipe(STDIN_FILENO);
bool succeeded = true;
if (std_input) {
succeeded &= base::WriteFileDescriptor(stdin_fd, *std_input);
}
succeeded &= IGNORE_EINTR(close(stdin_fd)) == 0;
// Kill the process if writing to or closing the pipe fails.
if (!succeeded) {
process.Kill(SIGKILL, 0);
}
result = process.Wait();
if (out && !process.GetOutput(out)) {
PLOG(ERROR) << "Failed to get process output";
return 1;
}
}
if (result != 0) {
std::string error_msg;
process.GetError(&error_msg);
LOG(ERROR) << "Child process exited with status " << result;
LOG(ERROR) << "stderr was: " << error_msg;
}
return result;
}
// Runs lpadmin with the provided |arg_list| and |std_input|.
int LpToolsImpl::Lpadmin(const ProcessWithOutput::ArgList& arg_list,
bool inherit_usergroups,
const base::EnvironmentMap& env,
const std::vector<uint8_t>* std_input) {
// Run in lp group so we can read and write /run/cups/cups.sock.
return RunAsUser(kLpadminUser, kLpGroup, kLpadminCommand,
kLpadminSeccompPolicy, arg_list, std_input,
inherit_usergroups, env);
}
// Runs lpstat with the provided |arg_list| and |std_input|.
int LpToolsImpl::Lpstat(const ProcessWithOutput::ArgList& arg_list,
std::string* output) {
// Run in lp group so we can read and write /run/cups/cups.sock.
return RunAsUser(kLpadminUser, kLpGroup, kLpstatCommand, kLpstatSeccompPolicy,
arg_list,
/*std_input=*/nullptr,
/*inherit_usergroups=*/false,
/*env=*/{}, output);
}
int LpToolsImpl::CupsTestPpd(const std::vector<uint8_t>& ppd_content) const {
std::string output;
int retval = RunAsUser(kLpadminUser, kLpadminGroup, kTestPPDCommand,
kTestPPDSeccompPolicy,
{"-W", "translations", "-W", "constraints", "-"},
&ppd_content, false, {}, &output);
// If there was an error running cupstestppd, log just the failure lines since
// logging all of the output can be too noisy. However, if there are no
// failure lines, just log everything.
if (retval != 0) {
const std::string fail_string = "FAIL";
const bool log_everything = output.find(fail_string) == std::string::npos;
LOG(ERROR) << "CupsTestPpd failures: ";
for (const std::string& line : base::SplitString(
output, "\n", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY)) {
if (log_everything || line.find(fail_string) != std::string::npos) {
LOG(ERROR) << line;
}
}
}
return retval;
}
int LpToolsImpl::CupsUriHelper(const std::string& uri) const {
std::string helper_path;
if (!GetHelperPath(kUriHelperBasename, &helper_path)) {
DCHECK(false) << "GetHelperPath() failed to return the CUPS URI helper!";
return 127; // Shell exit code for command not found.
}
ProcessWithOutput::ArgList args = {uri};
return RunAsUser(SandboxedProcess::kDefaultUser,
SandboxedProcess::kDefaultGroup, helper_path,
kUriHelperSeccompPolicy, args);
}
const base::FilePath& LpToolsImpl::GetCupsPpdDir() const {
static const base::FilePath kCupsPpdDir("/var/cache/cups/printers/ppd");
return kCupsPpdDir;
}
int LpToolsImpl::Chown(const std::string& path,
uid_t owner,
gid_t group) const {
return chown(path.c_str(), owner, group);
}
} // namespace debugd