blob: e83900bfb8d098d2765b65b71c4aec7832d8804e [file] [log] [blame]
// Copyright (c) 2012 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 <poll.h>
#include <signal.h>
#include <stddef.h>
#include <sys/wait.h>
#include <string>
#include <base/command_line.h>
#include <base/files/scoped_temp_dir.h>
#include <base/logging.h>
#include <chromeos/process.h>
#include <chromeos/syslog_logging.h>
#include <gflags/gflags.h>
#include "vpn-manager/daemon.h"
#include "vpn-manager/ipsec_manager.h"
#include "vpn-manager/l2tp_manager.h"
#pragma GCC diagnostic ignored "-Wstrict-aliasing"
DEFINE_string(client_cert_id, "", "PKCS#11 slot with client certificate");
DEFINE_string(client_cert_slot, "", "PKCS#11 key ID for client certificate");
DEFINE_bool(debug, false, "Log debugging information");
DEFINE_string(psk_file, "", "File with IPsec pre-shared key");
DEFINE_string(remote_host, "", "VPN server hostname");
DEFINE_string(server_ca_file, "", "File with IPsec server CA in DER format");
DEFINE_string(server_id, "", "ID expected from server");
DEFINE_string(user_pin, "", "PKCS#11 User PIN");
DEFINE_string(xauth_credentials_file, "", "File with Xauth user credentials");
#pragma GCC diagnostic error "-Wstrict-aliasing"
using vpn_manager::IpsecManager;
using vpn_manager::L2tpManager;
using vpn_manager::ServiceManager;
// True if a signal has requested termination.
static bool s_terminate_request = false;
void HandleSignal(int sig_num) {
LOG(INFO) << "Caught signal " << sig_num;
switch(sig_num) {
case SIGTERM:
case SIGINT:
s_terminate_request = true;
break;
case SIGALRM:
break;
}
}
static void InstallSignalHandlers() {
struct sigaction sa;
memset(&sa, 0, sizeof(sa));
sa.sa_handler = HandleSignal;
sigaction(SIGTERM, &sa, NULL);
sigaction(SIGINT, &sa, NULL);
sigaction(SIGALRM, &sa, NULL);
}
static void LockDownUmask() {
// Only user and group may access configuration files we create.
umask(S_IWGRP | S_IROTH | S_IWOTH);
}
// Run the main event loop. The events to handle are:
// 1) timeout from poll
// 2) caught signal
// 3) stdout/err of child process ready
// 4) child process dies
static void RunEventLoop(IpsecManager* ipsec, L2tpManager* l2tp) {
do {
int status;
int ipsec_poll_timeout = ipsec->Poll();
int l2tp_poll_timeout = l2tp->Poll();
int poll_timeout = (ipsec_poll_timeout > l2tp_poll_timeout) ?
ipsec_poll_timeout : l2tp_poll_timeout;
const int poll_input_count = 3;
struct pollfd poll_inputs[poll_input_count] = {
{ ipsec->output_fd(), POLLIN }, // ipsec output
{ l2tp->output_fd(), POLLIN }, // l2tp output
{ l2tp->ppp_output_fd(), POLLIN } // ppp output
};
int poll_result = poll(poll_inputs, poll_input_count, poll_timeout);
if (poll_result < 0 && errno != EINTR) {
int saved_errno = errno;
LOG(ERROR) << "Unexpected poll error: " << saved_errno;
return;
}
// Check if there are any child processes to be reaped without
// blocking.
pid_t pid = waitpid(-1, &status, WNOHANG);
if (pid > 0 && (ipsec->IsChild(pid) || l2tp->IsChild(pid))) {
LOG(WARNING) << "Child process " << pid << " stopped early";
s_terminate_request = true;
}
if (poll_inputs[0].revents & POLLIN)
ipsec->ProcessOutput();
if (poll_inputs[1].revents & POLLIN)
l2tp->ProcessOutput();
if (poll_inputs[2].revents & POLLIN)
l2tp->ProcessPppOutput();
} while(!ipsec->was_stopped() && !s_terminate_request);
}
int main(int argc, char* argv[]) {
base::ScopedTempDir temp_dir;
CommandLine::Init(argc, argv);
google::ParseCommandLineFlags(&argc, &argv, true);
int log_flags = chromeos::kLogToSyslog;
if (isatty(STDOUT_FILENO)) log_flags |= chromeos::kLogToStderr;
chromeos::InitLog(log_flags);
chromeos::OpenLog("l2tpipsec_vpn", true);
IpsecManager ipsec;
L2tpManager l2tp;
LockDownUmask();
ipsec.set_debug(FLAGS_debug);
l2tp.set_debug(FLAGS_debug);
ServiceManager::InitializeDirectories(&temp_dir);
struct sockaddr remote_address;
if (!ServiceManager::ResolveNameToSockAddr(FLAGS_remote_host,
&remote_address)) {
LOG(ERROR) << "Unable to resolve hostname " << FLAGS_remote_host;
return vpn_manager::kServiceErrorResolveHostnameFailed;
}
if (FLAGS_psk_file.empty() && !FLAGS_xauth_credentials_file.empty()) {
LOG(ERROR) << "Providing XAUTH credentials without a PSK is invalid";
return vpn_manager::kServiceErrorInvalidArgument;
}
if (!ipsec.Initialize(1,
remote_address,
FLAGS_psk_file,
FLAGS_xauth_credentials_file,
FLAGS_server_ca_file,
FLAGS_server_id,
FLAGS_client_cert_slot,
FLAGS_client_cert_id,
FLAGS_user_pin)) {
return ipsec.GetError();
}
if (!l2tp.Initialize(remote_address)) {
return l2tp.GetError();
}
ServiceManager::SetLayerOrder(&ipsec, &l2tp);
InstallSignalHandlers();
if (!ipsec.Start()) {
LOG(ERROR) << "Unable to start IPsec layer";
return ipsec.GetError();
}
RunEventLoop(&ipsec, &l2tp);
LOG(INFO) << "Shutting down...";
l2tp.Stop();
return ipsec.GetError();
}