| // Copyright 2017 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. |
| |
| #include <fcntl.h> |
| #include <string.h> |
| #include <sys/socket.h> |
| #include <sys/un.h> |
| |
| #include <linux/vm_sockets.h> |
| |
| #include <memory> |
| |
| #include <base/at_exit.h> |
| #include <base/files/scoped_file.h> |
| #include <base/files/file_path.h> |
| #include <base/files/file_util.h> |
| #include <base/memory/ref_counted.h> |
| #include <base/logging.h> |
| #include <base/strings/stringprintf.h> |
| #include <brillo/file_utils.h> |
| #include <brillo/flag_helper.h> |
| #include <brillo/syslog_logging.h> |
| #include <chromeos/dbus/service_constants.h> |
| #include <dbus/bus.h> |
| #include <dbus/message.h> |
| #include <dbus/object_proxy.h> |
| #include <grpcpp/grpcpp.h> |
| #include <vm_protos/proto_bindings/vm_host.grpc.pb.h> |
| |
| #include "vm_tools/syslog/forwarder.h" |
| |
| namespace { |
| constexpr unsigned int kPort = 9999; |
| // Default syslogd path. When |FLAGS_log_destination| is |kDevLog| we forward |
| // logs using a unix domain socket. |
| constexpr char kDevLog[] = "/dev/log"; |
| // Cryptohome token to be replaced in |FLAGS_log_destination|. |
| constexpr char kCryptohome[] = "cryptohome/"; |
| // Cryptohome root base path. |
| constexpr char kCryptohomeRoot[] = "/home/root"; |
| } // namespace |
| |
| std::string GetPrimaryUserHash() { |
| dbus::Bus::Options options; |
| options.bus_type = dbus::Bus::SYSTEM; |
| scoped_refptr<dbus::Bus> bus(new dbus::Bus(options)); |
| |
| if (!bus->Connect()) { |
| PLOG(ERROR) << "Failed to connect to system D-Bus"; |
| } |
| |
| auto session_manager_proxy = bus->GetObjectProxy( |
| login_manager::kSessionManagerServiceName, |
| dbus::ObjectPath(login_manager::kSessionManagerServicePath)); |
| |
| dbus::MethodCall method_call( |
| login_manager::kSessionManagerInterface, |
| login_manager::kSessionManagerRetrievePrimarySession); |
| std::unique_ptr<dbus::Response> response = |
| session_manager_proxy->CallMethodAndBlock( |
| &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT); |
| |
| if (!response.get()) { |
| PLOG(ERROR) << "Cannot retrieve username for primary session."; |
| return ""; |
| } |
| dbus::MessageReader response_reader(response.get()); |
| std::string username; |
| if (!response_reader.PopString(&username)) { |
| PLOG(ERROR) << "Primary session username bad format."; |
| return ""; |
| } |
| std::string sanitized_username; |
| if (!response_reader.PopString(&sanitized_username)) { |
| PLOG(ERROR) << "Primary session sanitized username bad format."; |
| return ""; |
| } |
| if (sanitized_username.empty()) { |
| PLOG(ERROR) << "Primary session does not exist."; |
| return ""; |
| } |
| return sanitized_username; |
| } |
| |
| base::FilePath ReplaceCryptohome(base::StringPiece in_path) { |
| if (in_path.starts_with(kCryptohome)) { |
| std::string user_hash = GetPrimaryUserHash(); |
| if (!user_hash.empty()) { |
| in_path.remove_prefix(base::StringPiece(kCryptohome).length()); |
| base::FilePath path = |
| base::FilePath(kCryptohomeRoot).Append(user_hash).Append(in_path); |
| // Ensure the parent dir exists. |
| base::FilePath parent_dir = path.DirName(); |
| if (!base::DirectoryExists(parent_dir)) { |
| base::File::Error dir_error; |
| if (!base::CreateDirectoryAndGetError(parent_dir, &dir_error)) { |
| LOG(ERROR) << "Failed to create directory in /home/root: " |
| << base::File::ErrorToString(dir_error); |
| return base::FilePath(in_path); |
| } |
| } |
| return path; |
| } |
| } |
| return base::FilePath(in_path); |
| } |
| |
| int main(int argc, char** argv) { |
| base::AtExitManager at_exit; |
| brillo::InitLog(brillo::kLogToSyslog | brillo::kLogToStderrIfTty); |
| |
| DEFINE_string( |
| log_destination, kDevLog, |
| "Path to unix domain datagram socket to which logs will be forwarded"); |
| brillo::FlagHelper::Init(argc, argv, "VM log forwarding tool"); |
| |
| bool is_socket_dest = true; |
| base::ScopedFD dest; |
| if (FLAGS_log_destination == kDevLog) { |
| dest.reset(socket(AF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC, 0)); |
| if (!dest.is_valid()) { |
| PLOG(ERROR) << "Failed to create unix domain datagram socket"; |
| return EXIT_FAILURE; |
| } |
| |
| struct sockaddr_un un = { |
| .sun_family = AF_UNIX, |
| }; |
| if (FLAGS_log_destination.size() >= sizeof(un.sun_path)) { |
| LOG(ERROR) << "Requested log destination path (" << FLAGS_log_destination |
| << ") is too long. Maximum path length: " |
| << sizeof(un.sun_path) << " characters"; |
| return EXIT_FAILURE; |
| } |
| |
| // sun_path is zero-initialized above so we just need to copy the path. |
| memcpy(un.sun_path, FLAGS_log_destination.c_str(), |
| FLAGS_log_destination.size()); |
| |
| if (connect(dest.get(), reinterpret_cast<struct sockaddr*>(&un), |
| sizeof(un)) != 0) { |
| PLOG(ERROR) << "Failed to connect to " << FLAGS_log_destination; |
| return EXIT_FAILURE; |
| } |
| } else { |
| is_socket_dest = false; |
| base::FilePath dest_path = ReplaceCryptohome(FLAGS_log_destination); |
| dest.reset(open(dest_path.value().c_str(), |
| O_CREAT | O_WRONLY | O_APPEND | O_CLOEXEC, 0640)); |
| if (!dest.is_valid()) { |
| PLOG(ERROR) << "Failed to open log file"; |
| return EXIT_FAILURE; |
| } |
| LOG(INFO) << "Vm log forwarder writing to " << dest_path; |
| } |
| |
| vm_tools::syslog::Forwarder forwarder(std::move(dest), is_socket_dest); |
| |
| grpc::ServerBuilder builder; |
| builder.AddListeningPort( |
| base::StringPrintf("vsock:%u:%u", VMADDR_CID_ANY, kPort), |
| grpc::InsecureServerCredentials()); |
| builder.RegisterService(&forwarder); |
| |
| std::unique_ptr<grpc::Server> server = builder.BuildAndStart(); |
| CHECK(server); |
| |
| LOG(INFO) << "VM log forwarder listening on port " << kPort; |
| |
| server->Wait(); |
| |
| return EXIT_SUCCESS; |
| } |