blob: 77cbcfd086733050e8bf7acd31c8c9f411aedde7 [file] [log] [blame]
// Copyright 2016 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// Tool to manipulate CUPS.
#include "debugd/src/cups_tool.h"
#include <signal.h>
#include <unistd.h>
#include <string>
#include <vector>
#include <base/files/file_util.h>
#include <base/logging.h>
#include <base/strings/string_piece.h>
#include <base/strings/string_util.h>
#include <chromeos/dbus/debugd/dbus-constants.h>
#include "debugd/src/constants.h"
#include "debugd/src/process_with_output.h"
namespace debugd {
namespace {
constexpr char kLpadminCommand[] = "/usr/sbin/lpadmin";
constexpr char kLpadminSeccompPolicy[] =
"/usr/share/policy/lpadmin-seccomp.policy";
constexpr char kTestPPDCommand[] = "/usr/bin/cupstestppd";
constexpr char kTestPPDSeccompPolicy[] =
"/usr/share/policy/cupstestppd-seccomp.policy";
constexpr char kLpadminUser[] = "lpadmin";
constexpr char kLpadminGroup[] = "lpadmin";
constexpr char kLpGroup[] = "lp";
// Returns the exit code for the executed process.
// By default disallow root mount namespace. Passing true as optional argument
// enables root mount namespace.
int 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 = nullptr,
bool root_mount_ns = false,
bool inherit_usergroups = false) {
ProcessWithOutput process;
process.set_separate_stderr(true);
process.SandboxAs(user, group);
if (!seccomp_policy.empty())
process.SetSeccompFilterPolicyFile(seccomp_policy);
if (root_mount_ns)
process.AllowAccessRootMountNamespace();
if (inherit_usergroups)
process.InheritUsergroups();
if (!process.Init())
return ProcessWithOutput::kRunError;
process.AddArg(command);
for (const std::string& arg : arg_list) {
process.AddArg(arg);
}
// Prepares a buffer with standard input.
std::vector<char> buf;
if (std_input != nullptr) {
buf.reserve(std_input->size());
for (uint8_t byte : *std_input) {
buf.push_back(static_cast<char>(byte));
}
}
// 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()) {
int stdin_fd = process.GetPipe(STDIN_FILENO);
// Kill the process if writing to or closing the pipe fails.
if (!base::WriteFileDescriptor(stdin_fd, buf.data(), buf.size()) ||
IGNORE_EINTR(close(stdin_fd)) < 0) {
process.Kill(SIGKILL, 0);
}
result = process.Wait();
}
if (result != 0) {
std::string error_msg;
process.GetError(&error_msg);
PLOG(ERROR) << "Child process failed" << error_msg;
}
return result;
}
// Runs cupstestppd on |ppd_content| returns the result code. 0 is the expected
// success code.
int TestPPD(const std::vector<uint8_t> & ppd_content) {
return RunAsUser(kLpadminUser, kLpadminGroup, kTestPPDCommand,
kTestPPDSeccompPolicy, {"-"}, &(ppd_content),
true /* root_mount_ns */);
}
// Runs lpadmin with the provided |arg_list| and |std_input|.
int Lpadmin(const ProcessWithOutput::ArgList& arg_list,
bool inherit_usergroups = false,
const std::vector<uint8_t>* std_input = nullptr) {
// Run in lp group so we can read and write /run/cups/cups.sock.
return RunAsUser(kLpadminUser, kLpGroup, kLpadminCommand,
kLpadminSeccompPolicy, arg_list, std_input,
false, inherit_usergroups);
}
// Checks whether the scheme for the given |uri| is one of the required schemes
// for IPP Everywhere.
bool IppEverywhereURI(const std::string& uri) {
static const char* const kValidSchemes[] = {"ipp://", "ipps://", "ippusb://"};
for (const char* scheme : kValidSchemes) {
if (base::StartsWith(uri, scheme, base::CompareCase::INSENSITIVE_ASCII))
return true;
}
return false;
}
// Evaluate true when a URI begins with a known scheme and has trailing
// characters (i.e. is not an empty URI).
bool UriHasKnownScheme(const std::string& uri) {
// Enumerate known printing URIs. Values are lifted from Chrome browser's
// Printer::GetProtocol().
const std::vector<std::string> known_schemes = {
"usb://",
"ipp://",
"ipps://",
"http://",
"https://",
"socket://",
"lpd://",
"ippusb://"
};
for (const std::string& scheme : known_schemes) {
if (base::StartsWith(uri, scheme, base::CompareCase::INSENSITIVE_ASCII) &&
(scheme.length() < uri.length())) {
return true;
}
}
return false;
}
// Determine whether a URI comprises mostly alphanumeric ASCII.
// Logic mirrors Chrome browser's CupsURIEscape.
bool UriIsGoodAscii(const std::string& uri) {
for (const char c : uri) {
if ((c == ' ') || (c == '%') || (c & 0x80)) {
return false;
}
}
return true;
}
} // namespace
// Invokes lpadmin with arguments to configure a new printer using '-m
// everywhere'. Returns 0 for success, 1 for failure, 4 if configuration
// failed.
int32_t CupsTool::AddAutoConfiguredPrinter(const std::string& name,
const std::string& uri) {
if (!IppEverywhereURI(uri)) {
LOG(WARNING) << "IPP, IPPS or IPPUSB required for IPP Everywhere: " << uri;
return CupsResult::CUPS_FATAL;
}
if (!CupsTool::UriSeemsReasonable(uri)) {
LOG(WARNING) << "Invalid URI: " << uri;
return CupsResult::CUPS_BAD_URI;
}
int32_t result;
if (base::StartsWith(uri, "ippusb://",
base::CompareCase::INSENSITIVE_ASCII)) {
// In the case of printing with the ippusb scheme, we want to run lpadmin in
// a minijail with the inherit usergroups option set.
result = Lpadmin({"-v", uri, "-p", name, "-m", "everywhere", "-E"}, true);
} else {
result = Lpadmin({"-v", uri, "-p", name, "-m", "everywhere", "-E"});
}
if (result != EXIT_SUCCESS) {
return CupsResult::CUPS_AUTOCONF_FAILURE;
}
return CupsResult::CUPS_SUCCESS;
}
int32_t CupsTool::AddManuallyConfiguredPrinter(
const std::string& name,
const std::string& uri,
const std::vector<uint8_t>& ppd_contents) {
int result = TestPPD(ppd_contents);
if (result != EXIT_SUCCESS) {
LOG(ERROR) << "PPD failed validation";
return CupsResult::CUPS_INVALID_PPD;
}
if (!CupsTool::UriSeemsReasonable(uri)) {
LOG(WARNING) << "Invalid URI: " << uri;
return CupsResult::CUPS_BAD_URI;
}
// lpadmin only returns 0 for success and 1 for failure.
result = Lpadmin({"-v", uri, "-p", name, "-P", "-", "-E"}, false,
&ppd_contents);
if (result != EXIT_SUCCESS) {
return CupsResult::CUPS_LPADMIN_FAILURE;
}
return CupsResult::CUPS_SUCCESS;
}
// Invokes lpadmin with -x to delete a printer.
bool CupsTool::RemovePrinter(const std::string& name) {
return Lpadmin({"-x", name}) == EXIT_SUCCESS;
}
// Check if a URI starts with a known scheme and comprises only
// non-whitespace ASCII.
bool CupsTool::UriSeemsReasonable(const std::string& uri) {
return UriHasKnownScheme(uri) && UriIsGoodAscii(uri);
}
} // namespace debugd