shill: openvpn option initialization.

This implements most of openvpn command line option initialization. The
remaining items that are needed for the basic autotest are covered by tracker
issues.

BUG=chromium-os:26992
TEST=unit tests

Change-Id: I7edc320b431e95535f14198c2025d5d50cd102ca
Reviewed-on: https://gerrit.chromium.org/gerrit/16960
Reviewed-by: Sam Leffler <sleffler@chromium.org>
Commit-Ready: Darin Petkov <petkov@chromium.org>
Reviewed-by: Darin Petkov <petkov@chromium.org>
Tested-by: Darin Petkov <petkov@chromium.org>
diff --git a/openvpn_driver.cc b/openvpn_driver.cc
index d4d2d6d..e50335d 100644
--- a/openvpn_driver.cc
+++ b/openvpn_driver.cc
@@ -4,11 +4,18 @@
 
 #include "shill/openvpn_driver.h"
 
+#include <base/logging.h>
+#include <chromeos/dbus/service_constants.h>
+
 #include "shill/error.h"
 
+using std::string;
+using std::vector;
+
 namespace shill {
 
-OpenVPNDriver::OpenVPNDriver() {}
+OpenVPNDriver::OpenVPNDriver(const KeyValueStore &args)
+    : args_(args) {}
 
 OpenVPNDriver::~OpenVPNDriver() {}
 
@@ -16,4 +23,136 @@
   error->Populate(Error::kNotSupported);
 }
 
+void OpenVPNDriver::InitOptions(vector<string> *options, Error *error) {
+  string vpnhost;
+  if (args_.ContainsString(flimflam::kProviderHostProperty)) {
+    vpnhost = args_.GetString(flimflam::kProviderHostProperty);
+  }
+  if (vpnhost.empty()) {
+    Error::PopulateAndLog(
+        error, Error::kInvalidArguments, "VPN host not specified.");
+    return;
+  }
+  options->push_back("--client");
+  options->push_back("--tls-client");
+  options->push_back("--remote");
+  options->push_back(vpnhost);
+  options->push_back("--nobind");
+  options->push_back("--persist-key");
+  options->push_back("--persist-tun");
+
+  // TODO(petkov): Add "--dev <interface_name>". For OpenVPN, the interface will
+  // be the tunnel device (crosbug.com/26841).
+  options->push_back("--dev-type");
+  options->push_back("tun");
+  options->push_back("--syslog");
+
+  // TODO(petkov): Enable verbosity based on shill logging options too.
+  AppendValueOption("OpenVPN.Verb", "--verb", options);
+
+  AppendValueOption("VPN.MTU", "--mtu", options);
+  AppendValueOption(flimflam::kOpenVPNProtoProperty, "--proto", options);
+  AppendValueOption(flimflam::kOpenVPNPortProperty, "--port", options);
+  AppendValueOption("OpenVPN.TLSAuth", "--tls-auth", options);
+
+  // TODO(petkov): Implement this.
+  LOG_IF(ERROR, args_.ContainsString(flimflam::kOpenVPNTLSAuthContentsProperty))
+      << "Support for --tls-auth not implemented yet.";
+
+  AppendValueOption(
+      flimflam::kOpenVPNTLSRemoteProperty, "--tls-remote", options);
+  AppendValueOption(flimflam::kOpenVPNCipherProperty, "--cipher", options);
+  AppendValueOption(flimflam::kOpenVPNAuthProperty, "--auth", options);
+  AppendFlag(flimflam::kOpenVPNAuthNoCacheProperty, "--auth-nocache", options);
+  AppendValueOption(
+      flimflam::kOpenVPNAuthRetryProperty, "--auth-retry", options);
+  AppendFlag(flimflam::kOpenVPNCompLZOProperty, "--comp-lzo", options);
+  AppendFlag(flimflam::kOpenVPNCompNoAdaptProperty, "--comp-noadapt", options);
+  AppendFlag(
+      flimflam::kOpenVPNPushPeerInfoProperty, "--push-peer-info", options);
+  AppendValueOption(flimflam::kOpenVPNRenegSecProperty, "--reneg-sec", options);
+  AppendValueOption(flimflam::kOpenVPNShaperProperty, "--shaper", options);
+  AppendValueOption(flimflam::kOpenVPNServerPollTimeoutProperty,
+                    "--server-poll-timeout", options);
+
+  // TODO(petkov): Implement this.
+  LOG_IF(ERROR, args_.ContainsString(flimflam::kOpenVPNCaCertNSSProperty))
+      << "Support for NSS CA not implemented yet.";
+
+  // Client-side ping support.
+  AppendValueOption("OpenVPN.Ping", "--ping", options);
+  AppendValueOption("OpenVPN.PingExit", "--ping-exit", options);
+  AppendValueOption("OpenVPN.PingRestart", "--ping-restart", options);
+
+  AppendValueOption(flimflam::kOpenVPNCaCertProperty, "--ca", options);
+  AppendValueOption("OpenVPN.Cert", "--cert", options);
+  AppendValueOption(
+      flimflam::kOpenVPNNsCertTypeProperty, "--ns-cert-type", options);
+  AppendValueOption("OpenVPN.Key", "--key", options);
+
+  // TODO(petkov): Implement this.
+  LOG_IF(ERROR, args_.ContainsString(flimflam::kOpenVPNClientCertIdProperty))
+      << "Support for PKCS#11 (--pkcs11-id and --pkcs11-providers) "
+      << "not implemented yet.";
+
+  // TLS suport.
+  string remote_cert_tls;
+  if (args_.ContainsString(flimflam::kOpenVPNRemoteCertTLSProperty)) {
+    remote_cert_tls = args_.GetString(flimflam::kOpenVPNRemoteCertTLSProperty);
+  }
+  if (remote_cert_tls.empty()) {
+    remote_cert_tls = "server";
+  }
+  if (remote_cert_tls != "none") {
+    options->push_back("--remote-cert-tls");
+    options->push_back(remote_cert_tls);
+  }
+
+  // This is an undocumented command line argument that works like a .cfg file
+  // entry. TODO(sleffler): Maybe roll this into --tls-auth?
+  AppendValueOption(
+      flimflam::kOpenVPNKeyDirectionProperty, "--key-direction", options);
+  // TODO(sleffler): Support more than one eku parameter.
+  AppendValueOption(
+      flimflam::kOpenVPNRemoteCertEKUProperty, "--remote-cert-eku", options);
+  AppendValueOption(
+      flimflam::kOpenVPNRemoteCertKUProperty, "--remote-cert-ku", options);
+
+  // TODO(petkov): Setup management control channel and add the approprate
+  // options (crosbug.com/26994).
+
+  // TODO(petkov): Setup openvpn-script options and DBus info required to send
+  // back Layer 3 configuration (crosbug.com/26993).
+
+  // Disable openvpn handling since we do route+ifconfig work.
+  options->push_back("--route-noexec");
+  options->push_back("--ifconfig-noexec");
+
+  // Drop root privileges on connection and enable callback scripts to send
+  // notify messages.
+  options->push_back("--user");
+  options->push_back("openvpn");
+  options->push_back("--group");
+  options->push_back("openvpn");
+}
+
+void OpenVPNDriver::AppendValueOption(
+    const string &property, const string &option, vector<string> *options) {
+  string value;
+  if (args_.ContainsString(property)) {
+    value = args_.GetString(property);
+  }
+  if (!value.empty()) {
+    options->push_back(option);
+    options->push_back(value);
+  }
+}
+
+void OpenVPNDriver::AppendFlag(
+    const string &property, const string &option, vector<string> *options) {
+  if (args_.ContainsString(property)) {
+    options->push_back(option);
+  }
+}
+
 }  // namespace shill
diff --git a/openvpn_driver.h b/openvpn_driver.h
index ad6926f..ea4b4c9 100644
--- a/openvpn_driver.h
+++ b/openvpn_driver.h
@@ -5,6 +5,12 @@
 #ifndef SHILL_OPENVPN_DRIVER_
 #define SHILL_OPENVPN_DRIVER_
 
+#include <string>
+#include <vector>
+
+#include <gtest/gtest_prod.h>  // for FRIEND_TEST
+
+#include "shill/key_value_store.h"
 #include "shill/vpn_driver.h"
 
 namespace shill {
@@ -13,13 +19,30 @@
 
 class OpenVPNDriver : public VPNDriver {
  public:
-  OpenVPNDriver();
+  explicit OpenVPNDriver(const KeyValueStore &args);
   virtual ~OpenVPNDriver();
 
   // Inherited from VPNDriver.
   virtual void Connect(Error *error);
 
  private:
+  friend class OpenVPNDriverTest;
+  FRIEND_TEST(OpenVPNDriverTest, AppendFlag);
+  FRIEND_TEST(OpenVPNDriverTest, AppendValueOption);
+  FRIEND_TEST(OpenVPNDriverTest, InitOptions);
+  FRIEND_TEST(OpenVPNDriverTest, InitOptionsNoHost);
+
+  void InitOptions(std::vector<std::string> *options, Error *error);
+
+  void AppendValueOption(const std::string &property,
+                         const std::string &option,
+                         std::vector<std::string> *options);
+  void AppendFlag(const std::string &property,
+                  const std::string &option,
+                  std::vector<std::string> *options);
+
+  KeyValueStore args_;
+
   DISALLOW_COPY_AND_ASSIGN(OpenVPNDriver);
 };
 
diff --git a/openvpn_driver_unittest.cc b/openvpn_driver_unittest.cc
index 08064c1..e757461 100644
--- a/openvpn_driver_unittest.cc
+++ b/openvpn_driver_unittest.cc
@@ -4,25 +4,109 @@
 
 #include "shill/openvpn_driver.h"
 
+#include <chromeos/dbus/service_constants.h>
 #include <gtest/gtest.h>
 
 #include "shill/error.h"
 
+using std::string;
+using std::vector;
+
 namespace shill {
 
 class OpenVPNDriverTest : public testing::Test {
  public:
-  OpenVPNDriverTest() {}
+  OpenVPNDriverTest()
+      : driver_(args_) {}
+
   virtual ~OpenVPNDriverTest() {}
 
  protected:
+  static const char kOption[];
+  static const char kProperty[];
+  static const char kValue[];
+  static const char kOption2[];
+  static const char kProperty2[];
+  static const char kValue2[];
+
+  void SetArgs() {
+    driver_.args_ = args_;
+  }
+
+  KeyValueStore args_;
   OpenVPNDriver driver_;
 };
 
+const char OpenVPNDriverTest::kOption[] = "--openvpn-option";
+const char OpenVPNDriverTest::kProperty[] = "OpenVPN.SomeProperty";
+const char OpenVPNDriverTest::kValue[] = "some-property-value";
+const char OpenVPNDriverTest::kOption2[] = "--openvpn-option2";
+const char OpenVPNDriverTest::kProperty2[] = "OpenVPN.SomeProperty2";
+const char OpenVPNDriverTest::kValue2[] = "some-property-value2";
+
 TEST_F(OpenVPNDriverTest, Connect) {
   Error error;
   driver_.Connect(&error);
   EXPECT_EQ(Error::kNotSupported, error.type());
 }
 
+TEST_F(OpenVPNDriverTest, InitOptionsNoHost) {
+  Error error;
+  vector<string> options;
+  driver_.InitOptions(&options, &error);
+  EXPECT_EQ(Error::kInvalidArguments, error.type());
+  EXPECT_TRUE(options.empty());
+}
+
+TEST_F(OpenVPNDriverTest, InitOptions) {
+  static const char kHost[] = "192.168.2.254";
+  args_.SetString(flimflam::kProviderHostProperty, kHost);
+  SetArgs();
+  Error error;
+  vector<string> options;
+  driver_.InitOptions(&options, &error);
+  EXPECT_TRUE(error.IsSuccess());
+  EXPECT_EQ("--client", options[0]);
+  EXPECT_EQ("--remote", options[2]);
+  EXPECT_EQ(kHost, options[3]);
+  EXPECT_EQ("openvpn", options.back());
+}
+
+TEST_F(OpenVPNDriverTest, AppendValueOption) {
+  vector<string> options;
+  driver_.AppendValueOption("OpenVPN.UnknownProperty", kOption, &options);
+  EXPECT_TRUE(options.empty());
+
+  args_.SetString(kProperty, "");
+  SetArgs();
+  driver_.AppendValueOption(kProperty, kOption, &options);
+  EXPECT_TRUE(options.empty());
+
+  args_.SetString(kProperty, kValue);
+  args_.SetString(kProperty2, kValue2);
+  SetArgs();
+  driver_.AppendValueOption(kProperty, kOption, &options);
+  driver_.AppendValueOption(kProperty2, kOption2, &options);
+  EXPECT_EQ(4, options.size());
+  EXPECT_EQ(kOption, options[0]);
+  EXPECT_EQ(kValue, options[1]);
+  EXPECT_EQ(kOption2, options[2]);
+  EXPECT_EQ(kValue2, options[3]);
+}
+
+TEST_F(OpenVPNDriverTest, AppendFlag) {
+  vector<string> options;
+  driver_.AppendValueOption("OpenVPN.UnknownProperty", kOption, &options);
+  EXPECT_TRUE(options.empty());
+
+  args_.SetString(kProperty, "");
+  args_.SetString(kProperty2, kValue2);
+  SetArgs();
+  driver_.AppendFlag(kProperty, kOption, &options);
+  driver_.AppendFlag(kProperty2, kOption2, &options);
+  EXPECT_EQ(2, options.size());
+  EXPECT_EQ(kOption, options[0]);
+  EXPECT_EQ(kOption2, options[1]);
+}
+
 }  // namespace shill
diff --git a/vpn_provider.cc b/vpn_provider.cc
index 39fa0e5..951e165 100644
--- a/vpn_provider.cc
+++ b/vpn_provider.cc
@@ -41,7 +41,7 @@
   const string &type = args.GetString(flimflam::kProviderTypeProperty);
   scoped_ptr<VPNDriver> driver;
   if (type == flimflam::kProviderOpenVpn) {
-    driver.reset(new OpenVPNDriver());
+    driver.reset(new OpenVPNDriver(args));
   } else {
     Error::PopulateAndLog(
         error, Error::kNotSupported, "Unsupported VPN type: " + type);