| // 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 <errno.h> |
| #include <fcntl.h> |
| #include <grp.h> |
| #include <poll.h> |
| #include <pwd.h> |
| #include <signal.h> |
| #include <string.h> |
| #include <termios.h> |
| #include <unistd.h> |
| |
| #include <sys/ioctl.h> |
| #include <sys/signalfd.h> |
| #include <sys/socket.h> |
| #include <sys/types.h> |
| #include <sys/wait.h> |
| |
| #include <linux/vm_sockets.h> // Needs to come after sys/socket.h |
| |
| #include <algorithm> |
| #include <memory> |
| #include <string> |
| |
| #include <base/at_exit.h> |
| #include <base/bind.h> |
| #include <base/bind_helpers.h> |
| #include <base/files/file_util.h> |
| #include <base/files/scoped_file.h> |
| #include <base/location.h> |
| #include <base/logging.h> |
| #include <base/macros.h> |
| #include <base/posix/eintr_wrapper.h> |
| #include <base/stl_util.h> |
| #include <base/strings/string_split.h> |
| #include <base/strings/stringprintf.h> |
| #include <brillo/asynchronous_signal_handler.h> |
| #include <brillo/flag_helper.h> |
| #include <brillo/message_loops/base_message_loop.h> |
| #include <brillo/syslog_logging.h> |
| #include <chromeos/constants/vm_tools.h> |
| #include <vm_protos/proto_bindings/vsh.pb.h> |
| |
| #include "vm_tools/vsh/utils.h" |
| #include "vm_tools/vsh/vsh_forwarder.h" |
| |
| using std::string; |
| using vm_tools::vsh::RecvMessage; |
| using vm_tools::vsh::SendMessage; |
| using vm_tools::vsh::Shutdown; |
| using vm_tools::vsh::VshForwarder; |
| |
| int main(int argc, char** argv) { |
| base::AtExitManager exit_manager; |
| brillo::InitLog(brillo::kLogToSyslog | brillo::kLogToStderrIfTty); |
| |
| DEFINE_uint64(forward_to_host_port, 0, "Port to forward to on the host"); |
| DEFINE_bool(inherit_env, false, "Inherit the current environment variables"); |
| DEFINE_string(default_user, "chronos", "Default login user"); |
| DEFINE_bool(allow_to_switch_user, true, |
| "Allow to switch to another user on login"); |
| |
| brillo::FlagHelper::Init(argc, argv, "vsh daemon"); |
| base::CommandLine* cl = base::CommandLine::ForCurrentProcess(); |
| if (cl->GetArgs().size() > 0) { |
| LOG(ERROR) << "Unknown extra command line arguments; exiting"; |
| return EXIT_FAILURE; |
| } |
| |
| if (FLAGS_forward_to_host_port != 0) { |
| uint32_t port = static_cast<uint32_t>(FLAGS_forward_to_host_port); |
| if (port != FLAGS_forward_to_host_port) { |
| LOG(ERROR) << "Port " << FLAGS_forward_to_host_port |
| << " is not a valid port"; |
| return EXIT_FAILURE; |
| } |
| |
| base::ScopedFD sock_fd(socket(AF_VSOCK, SOCK_STREAM | SOCK_CLOEXEC, 0)); |
| if (!sock_fd.is_valid()) { |
| PLOG(ERROR) << "Failed to open vsock socket"; |
| return EXIT_FAILURE; |
| } |
| |
| struct sockaddr_vm addr; |
| memset(&addr, 0, sizeof(addr)); |
| addr.svm_family = AF_VSOCK; |
| addr.svm_port = port; |
| addr.svm_cid = 2; |
| |
| if (HANDLE_EINTR(connect(sock_fd.get(), |
| reinterpret_cast<struct sockaddr*>(&addr), |
| sizeof(addr))) < 0) { |
| PLOG(ERROR) << "Failed to connect to vsh client"; |
| return EXIT_FAILURE; |
| } |
| |
| // Set up and start the message loop. |
| brillo::BaseMessageLoop message_loop; |
| message_loop.SetAsCurrent(); |
| auto forwarder = VshForwarder::Create(std::move(sock_fd), FLAGS_inherit_env, |
| std::move(FLAGS_default_user), |
| FLAGS_allow_to_switch_user); |
| |
| if (!forwarder) { |
| return EXIT_FAILURE; |
| } |
| |
| message_loop.Run(); |
| return EXIT_SUCCESS; |
| } |
| |
| // Create a socket to listen for incoming vsh connections. |
| base::ScopedFD sock_fd(socket(AF_VSOCK, SOCK_STREAM | SOCK_CLOEXEC, 0)); |
| if (!sock_fd.is_valid()) { |
| PLOG(ERROR) << "Failed to create socket"; |
| return EXIT_FAILURE; |
| } |
| |
| struct sockaddr_vm addr; |
| memset(&addr, 0, sizeof(addr)); |
| addr.svm_family = AF_VSOCK; |
| addr.svm_port = vm_tools::kVshPort; |
| addr.svm_cid = VMADDR_CID_ANY; |
| |
| if (bind(sock_fd.get(), reinterpret_cast<const struct sockaddr*>(&addr), |
| sizeof(addr)) < 0) { |
| PLOG(ERROR) << "Failed to bind vshd port"; |
| return EXIT_FAILURE; |
| } |
| |
| // Allow a backlog of up to 32 connections. This is exceedingly generous since |
| // this daemon forks after accepting a connection. |
| if (listen(sock_fd.get(), 32) < 0) { |
| PLOG(ERROR) << "Failed to listen"; |
| return EXIT_FAILURE; |
| } |
| |
| // Block SIGCHLD and set up a signalfd so the main daemon can reap its |
| // children. |
| sigset_t sigchld_mask, saved_mask; |
| DCHECK_EQ(sigemptyset(&sigchld_mask), 0); |
| DCHECK_EQ(sigaddset(&sigchld_mask, SIGCHLD), 0); |
| if (sigprocmask(SIG_BLOCK, &sigchld_mask, &saved_mask) < 0) { |
| PLOG(ERROR) << "Failed to block SIGCHLD"; |
| return EXIT_FAILURE; |
| } |
| |
| base::ScopedFD signal_fd( |
| signalfd(-1, &sigchld_mask, SFD_NONBLOCK | SFD_CLOEXEC)); |
| if (!signal_fd.is_valid()) { |
| PLOG(ERROR) << "Failed to set up signalfd"; |
| return EXIT_FAILURE; |
| } |
| |
| struct pollfd pollfds[] = { |
| {signal_fd.get(), POLLIN, 0}, |
| {sock_fd.get(), POLLIN, 0}, |
| }; |
| const int num_pollfds = base::size(pollfds); |
| |
| while (true) { |
| if (poll(pollfds, num_pollfds, -1) < 0) { |
| PLOG(ERROR) << "Failed to poll"; |
| return EXIT_FAILURE; |
| } |
| |
| for (int i = 0; i < num_pollfds; i++) { |
| if (!(pollfds[i].revents & POLLIN)) |
| continue; |
| |
| if (i == 0) { |
| // signalfd. |
| struct signalfd_siginfo siginfo; |
| if (read(signal_fd.get(), &siginfo, sizeof(siginfo)) != |
| sizeof(siginfo)) { |
| PLOG(ERROR) << "Failed to read entire signalfd siginfo"; |
| continue; |
| } |
| DCHECK_EQ(siginfo.ssi_signo, SIGCHLD); |
| |
| // Reap any child exit statuses. |
| while (waitpid(-1, nullptr, WNOHANG) > 0) |
| continue; |
| } else if (i == 1) { |
| // sock_fd. |
| struct sockaddr_vm peer_addr; |
| socklen_t addr_size = sizeof(peer_addr); |
| base::ScopedFD peer_sock_fd(HANDLE_EINTR(accept4( |
| sock_fd.get(), reinterpret_cast<struct sockaddr*>(&peer_addr), |
| &addr_size, SOCK_CLOEXEC))); |
| if (!peer_sock_fd.is_valid()) { |
| PLOG(ERROR) << "Failed to accept connection from client"; |
| continue; |
| } |
| |
| int pid = fork(); |
| |
| if (pid == 0) { |
| // The child needs to restore the original signal mask, and close |
| // the listening sock_fd and signalfd manually. These fds will be |
| // closed automatically on exec() anyway, but it's better not to allow |
| // the unprivileged forwarder to have access to either of these. |
| if (sigprocmask(SIG_SETMASK, &saved_mask, nullptr) < 0) { |
| PLOG(ERROR) << "Failed to restore signal mask after fork"; |
| } |
| sock_fd.reset(); |
| signal_fd.reset(); |
| // Set up and start the message loop. |
| brillo::BaseMessageLoop message_loop; |
| message_loop.SetAsCurrent(); |
| auto forwarder = VshForwarder::Create( |
| std::move(peer_sock_fd), FLAGS_inherit_env, |
| std::move(FLAGS_default_user), FLAGS_allow_to_switch_user); |
| |
| if (!forwarder) { |
| return EXIT_FAILURE; |
| } |
| |
| message_loop.Run(); |
| return EXIT_SUCCESS; |
| } |
| } |
| } |
| } |
| |
| return EXIT_SUCCESS; |
| } |