shill: OpenVPNDriver: Write a configuration file

Instead of passing configuration to OpenVPN using command line
options, write out a configuration file instead.  This config
file is owned by root created in a run directory that is not
readable by any other users.  Although OpenVPN drops privileges,
it reads its configuration before doing so.  The configuration
file is removed with the regular OpenVPNDriver cleanup process.

As a side effect of this, all added options in the OpenVPNDriver
and OpenVPNManagementServer now lose their "--" prefix.

BUG=chromium:217624
TEST=Unit tests, network_VPNConnect.openvpn_user_pass

Change-Id: I6424ccafb5764428b1ee8fc2ad41177a6d2b3c52
Reviewed-on: https://gerrit.chromium.org/gerrit/64368
Commit-Queue: Paul Stewart <pstew@chromium.org>
Reviewed-by: Paul Stewart <pstew@chromium.org>
Tested-by: Paul Stewart <pstew@chromium.org>
diff --git a/openvpn_driver.cc b/openvpn_driver.cc
index 2ef1e7f..60f67ee 100644
--- a/openvpn_driver.cc
+++ b/openvpn_driver.cc
@@ -118,6 +118,12 @@
 const char OpenVPNDriver::kChromeOSReleaseName[] = "CHROMEOS_RELEASE_NAME";
 const char OpenVPNDriver::kChromeOSReleaseVersion[] =
     "CHROMEOS_RELEASE_VERSION";
+
+// Directory where OpenVPN configuration files are exported while the
+// process is running.
+const char OpenVPNDriver::kDefaultOpenVPNConfigurationDirectory[] =
+    RUNDIR "/openvpn_config";
+
 const int OpenVPNDriver::kReconnectOfflineTimeoutSeconds = 2 * 60;
 const int OpenVPNDriver::kReconnectTLSErrorTimeoutSeconds = 20;
 
@@ -137,6 +143,7 @@
       certificate_file_(new CertificateFile()),
       process_killer_(ProcessKiller::GetInstance()),
       lsb_release_file_(kLSBReleaseFile),
+      openvpn_config_directory_(kDefaultOpenVPNConfigurationDirectory),
       pid_(0),
       child_watch_tag_(0),
       default_service_callback_tag_(0) {}
@@ -174,6 +181,10 @@
     file_util::Delete(tls_auth_file_, false);
     tls_auth_file_.clear();
   }
+  if (!openvpn_config_file_.empty()) {
+    file_util::Delete(openvpn_config_file_, false);
+    openvpn_config_file_.clear();
+  }
   if (default_service_callback_tag_) {
     manager()->DeregisterDefaultServiceCallback(default_service_callback_tag_);
     default_service_callback_tag_ = 0;
@@ -213,12 +224,42 @@
 }
 
 // static
-string OpenVPNDriver::JoinOptions(const vector<vector<string>> &options) {
+string OpenVPNDriver::JoinOptions(const vector<vector<string>> &options,
+                                  char separator) {
   vector<string> option_strings;
   for (const auto &option : options) {
     option_strings.push_back(JoinString(option, ' '));
   }
-  return JoinString(option_strings, ',');
+  return JoinString(option_strings, separator);
+}
+
+bool OpenVPNDriver::WriteConfigFile(
+    const vector<vector<string>> &options,
+    FilePath *config_file) {
+  if (!file_util::DirectoryExists(openvpn_config_directory_)) {
+    if (!file_util::CreateDirectory(openvpn_config_directory_)) {
+      LOG(ERROR) << "Unable to create configuration directory  "
+                 << openvpn_config_directory_.value();
+      return false;
+    }
+    if (chmod(openvpn_config_directory_.value().c_str(), S_IRWXU)) {
+      LOG(ERROR) << "Failed to set permissions on "
+                 << openvpn_config_directory_.value();
+      file_util::Delete(openvpn_config_directory_, true);
+      return false;
+    }
+  }
+
+  string contents = JoinOptions(options, '\n');
+  contents.push_back('\n');
+  if (!file_util::CreateTemporaryFileInDir(openvpn_config_directory_,
+                                           config_file) ||
+      file_util::WriteFile(*config_file, contents.data(), contents.size()) !=
+      static_cast<int>(contents.size())) {
+    LOG(ERROR) << "Unable to setup OpenVPN config file.";
+    return false;
+  }
+  return true;
 }
 
 bool OpenVPNDriver::SpawnOpenVPN() {
@@ -230,17 +271,18 @@
   if (error.IsFailure()) {
     return false;
   }
-  LOG(INFO) << "OpenVPN process options: " << JoinOptions(options);
+  LOG(INFO) << "OpenVPN process options: " << JoinOptions(options, ',');
+  if (!WriteConfigFile(options, &openvpn_config_file_)) {
+    return false;
+  }
 
   // TODO(quiche): This should be migrated to use ExternalTask.
   // (crbug.com/246263).
   vector<char *> process_args;
   process_args.push_back(const_cast<char *>(kOpenVPNPath));
-  for (const auto &option : options) {
-    for (const auto &argument : option) {
-       process_args.push_back(const_cast<char *>(argument.c_str()));
-    }
-  }
+  process_args.push_back(const_cast<char *>("--config"));
+  process_args.push_back(const_cast<char *>(
+      openvpn_config_file_.value().c_str()));
   process_args.push_back(NULL);
 
   vector<string> environment;
@@ -522,32 +564,32 @@
         error, Error::kInvalidArguments, "VPN host not specified.");
     return;
   }
-  AppendOption("--client", options);
-  AppendOption("--tls-client", options);
+  AppendOption("client", options);
+  AppendOption("tls-client", options);
 
   string host_name, host_port;
   if (SplitPortFromHost(vpnhost, &host_name, &host_port)) {
     DCHECK(!host_name.empty());
     DCHECK(!host_port.empty());
-    AppendOption("--remote", host_name, host_port, options);
+    AppendOption("remote", host_name, host_port, options);
   } else {
-    AppendOption("--remote", vpnhost, options);
+    AppendOption("remote", vpnhost, options);
   }
 
-  AppendOption("--nobind", options);
-  AppendOption("--persist-key", options);
-  AppendOption("--persist-tun", options);
+  AppendOption("nobind", options);
+  AppendOption("persist-key", options);
+  AppendOption("persist-tun", options);
 
   CHECK(!tunnel_interface_.empty());
-  AppendOption("--dev", tunnel_interface_, options);
-  AppendOption("--dev-type", "tun", options);
+  AppendOption("dev", tunnel_interface_, options);
+  AppendOption("dev-type", "tun", options);
 
   InitLoggingOptions(options);
 
-  AppendValueOption(kVPNMTUProperty, "--mtu", options);
-  AppendValueOption(flimflam::kOpenVPNProtoProperty, "--proto", options);
-  AppendValueOption(flimflam::kOpenVPNPortProperty, "--port", options);
-  AppendValueOption(kOpenVPNTLSAuthProperty, "--tls-auth", options);
+  AppendValueOption(kVPNMTUProperty, "mtu", options);
+  AppendValueOption(flimflam::kOpenVPNProtoProperty, "proto", options);
+  AppendValueOption(flimflam::kOpenVPNPortProperty, "port", options);
+  AppendValueOption(kOpenVPNTLSAuthProperty, "tls-auth", options);
   {
     string contents =
         args()->LookupString(flimflam::kOpenVPNTLSAuthContentsProperty, "");
@@ -560,36 +602,36 @@
             error, Error::kInternalError, "Unable to setup tls-auth file.");
         return;
       }
-      AppendOption("--tls-auth", tls_auth_file_.value(), options);
+      AppendOption("tls-auth", tls_auth_file_.value(), options);
     }
   }
   AppendValueOption(
-      flimflam::kOpenVPNTLSRemoteProperty, "--tls-remote", options);
-  AppendValueOption(flimflam::kOpenVPNCipherProperty, "--cipher", options);
-  AppendValueOption(flimflam::kOpenVPNAuthProperty, "--auth", options);
-  AppendFlag(flimflam::kOpenVPNAuthNoCacheProperty, "--auth-nocache", options);
+      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);
+      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);
+      flimflam::kOpenVPNPushPeerInfoProperty, "push-peer-info", options);
+  AppendValueOption(flimflam::kOpenVPNRenegSecProperty, "reneg-sec", options);
+  AppendValueOption(flimflam::kOpenVPNShaperProperty, "shaper", options);
   AppendValueOption(flimflam::kOpenVPNServerPollTimeoutProperty,
-                    "--server-poll-timeout", options);
+                    "server-poll-timeout", options);
 
   if (!InitCAOptions(options, error)) {
     return;
   }
 
   // Client-side ping support.
-  AppendValueOption(kOpenVPNPingProperty, "--ping", options);
-  AppendValueOption(kOpenVPNPingExitProperty, "--ping-exit", options);
-  AppendValueOption(kOpenVPNPingRestartProperty, "--ping-restart", options);
+  AppendValueOption(kOpenVPNPingProperty, "ping", options);
+  AppendValueOption(kOpenVPNPingExitProperty, "ping-exit", options);
+  AppendValueOption(kOpenVPNPingRestartProperty, "ping-restart", options);
 
   AppendValueOption(
-      flimflam::kOpenVPNNsCertTypeProperty, "--ns-cert-type", options);
+      flimflam::kOpenVPNNsCertTypeProperty, "ns-cert-type", options);
 
   InitClientAuthOptions(options);
   InitPKCS11Options(options);
@@ -601,18 +643,18 @@
     remote_cert_tls = "server";
   }
   if (remote_cert_tls != "none") {
-    AppendOption("--remote-cert-tls", remote_cert_tls, options);
+    AppendOption("remote-cert-tls", remote_cert_tls, options);
   }
 
   // This is an undocumented command line argument that works like a .cfg file
-  // entry. TODO(sleffler): Maybe roll this into --tls-auth?
+  // entry. TODO(sleffler): Maybe roll this into the "tls-auth" option?
   AppendValueOption(
-      flimflam::kOpenVPNKeyDirectionProperty, "--key-direction", options);
+      flimflam::kOpenVPNKeyDirectionProperty, "key-direction", options);
   // TODO(sleffler): Support more than one eku parameter.
   AppendValueOption(
-      flimflam::kOpenVPNRemoteCertEKUProperty, "--remote-cert-eku", options);
+      flimflam::kOpenVPNRemoteCertEKUProperty, "remote-cert-eku", options);
   AppendValueOption(
-      flimflam::kOpenVPNRemoteCertKUProperty, "--remote-cert-ku", options);
+      flimflam::kOpenVPNRemoteCertKUProperty, "remote-cert-ku", options);
 
   if (!InitManagementChannelOptions(options, error)) {
     return;
@@ -620,24 +662,24 @@
 
   // Setup openvpn-script options and RPC information required to send back
   // Layer 3 configuration.
-  AppendOption("--setenv", kRPCTaskServiceVariable,
+  AppendOption("setenv", kRPCTaskServiceVariable,
                rpc_task_->GetRpcConnectionIdentifier(), options);
-  AppendOption("--setenv", kRPCTaskServiceVariable,
+  AppendOption("setenv", kRPCTaskServiceVariable,
                rpc_task_->GetRpcConnectionIdentifier(), options);
-  AppendOption("--setenv", kRPCTaskPathVariable, rpc_task_->GetRpcIdentifier(),
+  AppendOption("setenv", kRPCTaskPathVariable, rpc_task_->GetRpcIdentifier(),
                options);
-  AppendOption("--script-security", "2", options);
-  AppendOption("--up", kOpenVPNScript, options);
-  AppendOption("--up-restart", options);
+  AppendOption("script-security", "2", options);
+  AppendOption("up", kOpenVPNScript, options);
+  AppendOption("up-restart", options);
 
   // Disable openvpn handling since we do route+ifconfig work.
-  AppendOption("--route-noexec", options);
-  AppendOption("--ifconfig-noexec", options);
+  AppendOption("route-noexec", options);
+  AppendOption("ifconfig-noexec", options);
 
   // Drop root privileges on connection and enable callback scripts to send
   // notify messages.
-  AppendOption("--user", "openvpn", options);
-  AppendOption("--group", "openvpn", options);
+  AppendOption("user", "openvpn", options);
+  AppendOption("group", "openvpn", options);
 }
 
 bool OpenVPNDriver::InitCAOptions(
@@ -660,7 +702,7 @@
       num_ca_cert_types++;
   if (num_ca_cert_types == 0) {
     // Use default CAs if no CA certificate is provided.
-    AppendOption("--ca", kDefaultCACertificates, options);
+    AppendOption("ca", kDefaultCACertificates, options);
     return true;
   } else if (num_ca_cert_types > 1) {
     Error::PopulateAndLog(
@@ -681,7 +723,7 @@
           "Unable to extract NSS CA certificate: " + ca_cert_nss);
       return false;
     }
-    AppendOption("--ca", certfile.value(), options);
+    AppendOption("ca", certfile.value(), options);
     return true;
   } else if (!ca_cert_pem.empty()) {
     DCHECK(ca_cert.empty() && ca_cert_nss.empty());
@@ -693,11 +735,11 @@
           "Unable to extract PEM CA certificates.");
       return false;
     }
-    AppendOption("--ca", certfile.value(), options);
+    AppendOption("ca", certfile.value(), options);
     return true;
   }
   DCHECK(!ca_cert.empty() && ca_cert_nss.empty() && ca_cert_pem.empty());
-  AppendOption("--ca", ca_cert, options);
+  AppendOption("ca", ca_cert, options);
   return true;
 }
 
@@ -709,22 +751,22 @@
     if (provider.empty()) {
       provider = kDefaultPKCS11Provider;
     }
-    AppendOption("--pkcs11-providers", provider, options);
-    AppendOption("--pkcs11-id", id, options);
+    AppendOption("pkcs11-providers", provider, options);
+    AppendOption("pkcs11-id", id, options);
   }
 }
 
 void OpenVPNDriver::InitClientAuthOptions(vector<vector<string>> *options) {
-  bool has_cert = AppendValueOption(kOpenVPNCertProperty, "--cert", options) ||
+  bool has_cert = AppendValueOption(kOpenVPNCertProperty, "cert", options) ||
       !args()->LookupString(flimflam::kOpenVPNClientCertIdProperty, "").empty();
-  bool has_key = AppendValueOption(kOpenVPNKeyProperty, "--key", options);
+  bool has_key = AppendValueOption(kOpenVPNKeyProperty, "key", options);
   // If the AuthUserPass property is set, or the User property is non-empty, or
   // there's neither a key, nor a cert available, specify user-password client
   // authentication.
   if (args()->ContainsString(flimflam::kOpenVPNAuthUserPassProperty) ||
       !args()->LookupString(flimflam::kOpenVPNUserProperty, "").empty() ||
       (!has_cert && !has_key)) {
-    AppendOption("--auth-user-pass", options);
+    AppendOption("auth-user-pass", options);
   }
 }
 
@@ -746,14 +788,14 @@
 }
 
 void OpenVPNDriver::InitLoggingOptions(vector<vector<string>> *options) {
-  AppendOption("--syslog", options);
+  AppendOption("syslog", options);
 
   string verb = args()->LookupString(kOpenVPNVerbProperty, "");
   if (verb.empty() && SLOG_IS_ON(VPN, 0)) {
     verb = "3";
   }
   if (!verb.empty()) {
-    AppendOption("--verb", verb, options);
+    AppendOption("verb", verb, options);
   }
 }
 
diff --git a/openvpn_driver.h b/openvpn_driver.h
index 727df96..b544225 100644
--- a/openvpn_driver.h
+++ b/openvpn_driver.h
@@ -141,6 +141,7 @@
   FRIEND_TEST(OpenVPNDriverTest, SpawnOpenVPN);
   FRIEND_TEST(OpenVPNDriverTest, SplitPortFromHost);
   FRIEND_TEST(OpenVPNDriverTest, VerifyPaths);
+  FRIEND_TEST(OpenVPNDriverTest, WriteConfigFile);
 
   // The map is a sorted container that allows us to iterate through the options
   // in order.
@@ -157,6 +158,8 @@
   static const char kChromeOSReleaseName[];
   static const char kChromeOSReleaseVersion[];
 
+  static const char kDefaultOpenVPNConfigurationDirectory[];
+
   static const int kReconnectOfflineTimeoutSeconds;
   static const int kReconnectTLSErrorTimeoutSeconds;
 
@@ -211,7 +214,11 @@
 
   // Join a list of options into a single string.
   static std::string JoinOptions(
-      const std::vector<std::vector<std::string>> &options);
+      const std::vector<std::vector<std::string>> &options, char separator);
+
+  // Output an OpenVPN configuration.
+  bool WriteConfigFile(const std::vector<std::vector<std::string>> &options,
+                       base::FilePath *config_file);
 
   // Called when the openpvn process exits.
   static void OnOpenVPNDied(GPid pid, gint status, gpointer data);
@@ -249,6 +256,8 @@
   std::string tunnel_interface_;
   VirtualDeviceRefPtr device_;
   base::FilePath tls_auth_file_;
+  base::FilePath openvpn_config_directory_;
+  base::FilePath openvpn_config_file_;
   IPConfig::Properties ip_properties_;
 
   // The PID of the spawned openvpn process. May be 0 if no process has been
diff --git a/openvpn_driver_unittest.cc b/openvpn_driver_unittest.cc
index 1931950..72bc923 100644
--- a/openvpn_driver_unittest.cc
+++ b/openvpn_driver_unittest.cc
@@ -8,7 +8,9 @@
 
 #include <base/file_path.h>
 #include <base/file_util.h>
+#include <base/files/scoped_temp_dir.h>
 #include <base/string_util.h>
+#include <base/stringprintf.h>
 #include <chromeos/dbus/service_constants.h>
 #include <gtest/gtest.h>
 
@@ -74,6 +76,9 @@
     driver_->nss_ = &nss_;
     driver_->certificate_file_.reset(certificate_file_);  // Passes ownership.
     driver_->process_killer_ = &process_killer_;
+    temporary_directory_.CreateUniqueTempDir();
+    driver_->openvpn_config_directory_ =
+        temporary_directory_.path().Append(kOpenVPNConfigDirectory);
   }
 
   virtual ~OpenVPNDriverTest() {}
@@ -105,6 +110,7 @@
   static const char kNetwork2[];
   static const char kInterfaceName[];
   static const int kInterfaceIndex;
+  static const char kOpenVPNConfigDirectory[];
 
   void SetArg(const string &arg, const string &value) {
     driver_->args()->SetString(arg, value);
@@ -210,6 +216,7 @@
   MockCertificateFile *certificate_file_;  // Owned by |driver_|.
   MockNSS nss_;
   MockProcessKiller process_killer_;
+  base::ScopedTempDir temporary_directory_;
 
   // Owned by |driver_|.
   NiceMock<MockOpenVPNManagementServer> *management_server_;
@@ -217,10 +224,10 @@
   FilePath lsb_release_file_;
 };
 
-const char OpenVPNDriverTest::kOption[] = "--openvpn-option";
+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::kOption2[] = "openvpn-option2";
 const char OpenVPNDriverTest::kProperty2[] = "OpenVPN.SomeProperty2";
 const char OpenVPNDriverTest::kValue2[] = "some-property-value2";
 const char OpenVPNDriverTest::kGateway1[] = "10.242.2.13";
@@ -231,6 +238,7 @@
 const char OpenVPNDriverTest::kNetwork2[] = "192.168.0.0";
 const char OpenVPNDriverTest::kInterfaceName[] = "tun0";
 const int OpenVPNDriverTest::kInterfaceIndex = 123;
+const char OpenVPNDriverTest::kOpenVPNConfigDirectory[] = "openvpn";
 
 void OpenVPNDriverTest::GetLogin(string */*user*/, string */*password*/) {}
 
@@ -587,23 +595,23 @@
   vector<vector<string>> options;
   driver_->InitOptions(&options, &error);
   EXPECT_TRUE(error.IsSuccess());
-  EXPECT_EQ(vector<string> { "--client" }, options[0]);
-  ExpectInFlags(options, "--remote", kHost);
-  ExpectInFlags(options, vector<string> { "--setenv", kRPCTaskPathVariable,
+  EXPECT_EQ(vector<string> { "client" }, options[0]);
+  ExpectInFlags(options, "remote", kHost);
+  ExpectInFlags(options, vector<string> { "setenv", kRPCTaskPathVariable,
                                           RPCTaskMockAdaptor::kRpcId });
-  ExpectInFlags(options, "--dev", kInterfaceName);
-  ExpectInFlags(options, "--group", "openvpn");
+  ExpectInFlags(options, "dev", kInterfaceName);
+  ExpectInFlags(options, "group", "openvpn");
   EXPECT_EQ(kInterfaceName, driver_->tunnel_interface_);
   ASSERT_FALSE(driver_->tls_auth_file_.empty());
-  ExpectInFlags(options, "--tls-auth", driver_->tls_auth_file_.value());
+  ExpectInFlags(options, "tls-auth", driver_->tls_auth_file_.value());
   string contents;
   EXPECT_TRUE(
       file_util::ReadFileToString(driver_->tls_auth_file_, &contents));
   EXPECT_EQ(kTLSAuthContents, contents);
-  ExpectInFlags(options, "--pkcs11-id", kID);
-  ExpectInFlags(options, "--ca", OpenVPNDriver::kDefaultCACertificates);
-  ExpectInFlags(options, "--syslog");
-  ExpectNotInFlags(options, "--auth-user-pass");
+  ExpectInFlags(options, "pkcs11-id", kID);
+  ExpectInFlags(options, "ca", OpenVPNDriver::kDefaultCACertificates);
+  ExpectInFlags(options, "syslog");
+  ExpectNotInFlags(options, "auth-user-pass");
 }
 
 TEST_F(OpenVPNDriverTest, InitOptionsHostWithPort) {
@@ -617,7 +625,7 @@
   vector<vector<string>> options;
   driver_->InitOptions(&options, &error);
   EXPECT_TRUE(error.IsSuccess());
-  ExpectInFlags(options, vector<string> { "--remote", "v.com", "1234" });
+  ExpectInFlags(options, vector<string> { "remote", "v.com", "1234" });
 }
 
 TEST_F(OpenVPNDriverTest, InitCAOptions) {
@@ -630,12 +638,12 @@
   vector<vector<string>> options;
   EXPECT_TRUE(driver_->InitCAOptions(&options, &error));
   EXPECT_TRUE(error.IsSuccess());
-  ExpectInFlags(options, "--ca", OpenVPNDriver::kDefaultCACertificates);
+  ExpectInFlags(options, "ca", OpenVPNDriver::kDefaultCACertificates);
 
   options.clear();
   SetArg(flimflam::kOpenVPNCaCertProperty, kCaCert);
   EXPECT_TRUE(driver_->InitCAOptions(&options, &error));
-  ExpectInFlags(options, "--ca", kCaCert);
+  ExpectInFlags(options, "ca", kCaCert);
   EXPECT_TRUE(error.IsSuccess());
 
   SetArg(flimflam::kOpenVPNCaCertNSSProperty, kCaCertNSS);
@@ -662,7 +670,7 @@
   error.Reset();
   options.clear();
   EXPECT_TRUE(driver_->InitCAOptions(&options, &error));
-  ExpectInFlags(options, "--ca", kNSSCertfile);
+  ExpectInFlags(options, "ca", kNSSCertfile);
   EXPECT_TRUE(error.IsSuccess());
 
   const vector<string> kCaCertPEM{ "---PEM CONTENTS---" };
@@ -689,7 +697,7 @@
   error.Reset();
   options.clear();
   EXPECT_TRUE(driver_->InitCAOptions(&options, &error));
-  ExpectInFlags(options, "--ca", kPEMCertfile);
+  ExpectInFlags(options, "ca", kPEMCertfile);
   EXPECT_TRUE(error.IsSuccess());
 }
 
@@ -699,39 +707,39 @@
 
   // No key or cert, assume user/password authentication.
   driver_->InitClientAuthOptions(&options);
-  ExpectInFlags(options, "--auth-user-pass");
-  ExpectNotInFlags(options, "--key");
-  ExpectNotInFlags(options, "--cert");
+  ExpectInFlags(options, "auth-user-pass");
+  ExpectNotInFlags(options, "key");
+  ExpectNotInFlags(options, "cert");
 
   // Cert available, no user/password.
   options.clear();
   SetArg(kOpenVPNCertProperty, kTestValue);
   driver_->InitClientAuthOptions(&options);
-  ExpectNotInFlags(options, "--auth-user-pass");
-  ExpectNotInFlags(options, "--key");
-  ExpectInFlags(options, "--cert", kTestValue);
+  ExpectNotInFlags(options, "auth-user-pass");
+  ExpectNotInFlags(options, "key");
+  ExpectInFlags(options, "cert", kTestValue);
 
   // Key available, no user/password.
   options.clear();
   SetArg(kOpenVPNKeyProperty, kTestValue);
   driver_->InitClientAuthOptions(&options);
-  ExpectNotInFlags(options, "--auth-user-pass");
-  ExpectInFlags(options, "--key", kTestValue);
+  ExpectNotInFlags(options, "auth-user-pass");
+  ExpectInFlags(options, "key", kTestValue);
 
   // Key available, AuthUserPass set.
   options.clear();
   SetArg(flimflam::kOpenVPNAuthUserPassProperty, kTestValue);
   driver_->InitClientAuthOptions(&options);
-  ExpectInFlags(options, "--auth-user-pass");
-  ExpectInFlags(options, "--key", kTestValue);
+  ExpectInFlags(options, "auth-user-pass");
+  ExpectInFlags(options, "key", kTestValue);
 
   // Key available, User set.
   options.clear();
   RemoveStringArg(flimflam::kOpenVPNAuthUserPassProperty);
   SetArg(flimflam::kOpenVPNUserProperty, "user");
   driver_->InitClientAuthOptions(&options);
-  ExpectInFlags(options, "--auth-user-pass");
-  ExpectInFlags(options, "--key", kTestValue);
+  ExpectInFlags(options, "auth-user-pass");
+  ExpectInFlags(options, "key", kTestValue);
 
   // Empty PKCS11 certificate id, no user/password/cert.
   options.clear();
@@ -740,37 +748,37 @@
   RemoveStringArg(flimflam::kOpenVPNUserProperty);
   SetArg(flimflam::kOpenVPNClientCertIdProperty, "");
   driver_->InitClientAuthOptions(&options);
-  ExpectInFlags(options, "--auth-user-pass");
-  ExpectNotInFlags(options, "--key");
-  ExpectNotInFlags(options, "--cert");
-  ExpectNotInFlags(options, "--pkcs11-id");
+  ExpectInFlags(options, "auth-user-pass");
+  ExpectNotInFlags(options, "key");
+  ExpectNotInFlags(options, "cert");
+  ExpectNotInFlags(options, "pkcs11-id");
 
   // Non-empty PKCS11 certificate id, no user/password/cert.
   options.clear();
   SetArg(flimflam::kOpenVPNClientCertIdProperty, kTestValue);
   driver_->InitClientAuthOptions(&options);
-  ExpectNotInFlags(options, "--auth-user-pass");
-  ExpectNotInFlags(options, "--key");
-  ExpectNotInFlags(options, "--cert");
+  ExpectNotInFlags(options, "auth-user-pass");
+  ExpectNotInFlags(options, "key");
+  ExpectNotInFlags(options, "cert");
   // The "--pkcs11-id" option is added in InitPKCS11Options(), not here.
-  ExpectNotInFlags(options, "--pkcs11-id");
+  ExpectNotInFlags(options, "pkcs11-id");
 
   // PKCS11 certificate id available, AuthUserPass set.
   options.clear();
   SetArg(flimflam::kOpenVPNAuthUserPassProperty, kTestValue);
   driver_->InitClientAuthOptions(&options);
-  ExpectInFlags(options, "--auth-user-pass");
-  ExpectNotInFlags(options, "--key");
-  ExpectNotInFlags(options, "--cert");
+  ExpectInFlags(options, "auth-user-pass");
+  ExpectNotInFlags(options, "key");
+  ExpectNotInFlags(options, "cert");
 
   // PKCS11 certificate id available, User set.
   options.clear();
   RemoveStringArg(flimflam::kOpenVPNAuthUserPassProperty);
   SetArg(flimflam::kOpenVPNUserProperty, "user");
   driver_->InitClientAuthOptions(&options);
-  ExpectInFlags(options, "--auth-user-pass");
-  ExpectNotInFlags(options, "--key");
-  ExpectNotInFlags(options, "--cert");
+  ExpectInFlags(options, "auth-user-pass");
+  ExpectNotInFlags(options, "key");
+  ExpectNotInFlags(options, "cert");
 }
 
 TEST_F(OpenVPNDriverTest, InitPKCS11Options) {
@@ -781,15 +789,15 @@
   static const char kID[] = "TestPKCS11ID";
   SetArg(flimflam::kOpenVPNClientCertIdProperty, kID);
   driver_->InitPKCS11Options(&options);
-  ExpectInFlags(options, "--pkcs11-id", kID);
-  ExpectInFlags(options, "--pkcs11-providers", "libchaps.so");
+  ExpectInFlags(options, "pkcs11-id", kID);
+  ExpectInFlags(options, "pkcs11-providers", "libchaps.so");
 
   static const char kProvider[] = "libpkcs11.so";
   SetArg(flimflam::kOpenVPNProviderProperty, kProvider);
   options.clear();
   driver_->InitPKCS11Options(&options);
-  ExpectInFlags(options, "--pkcs11-id", kID);
-  ExpectInFlags(options, "--pkcs11-providers", kProvider);
+  ExpectInFlags(options, "pkcs11-id", kID);
+  ExpectInFlags(options, "pkcs11-providers", kProvider);
 }
 
 TEST_F(OpenVPNDriverTest, InitManagementChannelOptionsServerFail) {
@@ -830,21 +838,21 @@
   ScopeLogger::GetInstance()->EnableScopesByName("-vpn");
   driver_->InitLoggingOptions(&options);
   ASSERT_EQ(1, options.size());
-  EXPECT_EQ(vector<string> { "--syslog" }, options[0]);
+  EXPECT_EQ(vector<string> { "syslog" }, options[0]);
   ScopeLogger::GetInstance()->EnableScopesByName("+vpn");
   options.clear();
   driver_->InitLoggingOptions(&options);
-  ExpectInFlags(options, "--verb", "3");
+  ExpectInFlags(options, "verb", "3");
   ScopeLogger::GetInstance()->EnableScopesByName("-vpn");
   SetArg("OpenVPN.Verb", "2");
   options.clear();
   driver_->InitLoggingOptions(&options);
-  ExpectInFlags(options, "--verb", "2");
+  ExpectInFlags(options, "verb", "2");
   ScopeLogger::GetInstance()->EnableScopesByName("+vpn");
   SetArg("OpenVPN.Verb", "1");
   options.clear();
   driver_->InitLoggingOptions(&options);
-  ExpectInFlags(options, "--verb", "1");
+  ExpectInFlags(options, "verb", "1");
   if (!vpn_logging) {
     ScopeLogger::GetInstance()->EnableScopesByName("-vpn");
   }
@@ -1224,4 +1232,35 @@
                 OpenVPNDriver::kReconnectReasonTLSError));
 }
 
+TEST_F(OpenVPNDriverTest, WriteConfigFile) {
+  const char kOption0[] = "option0";
+  const char kOption1[] = "option1";
+  const char kOption1Argument0[] = "option1-argument0";
+  const char kOption2[] = "option2";
+  const char kOption2Argument0[] = "option2-argument0";
+  const char kOption2Argument1[] = "option2-argument1";
+  vector<vector<string>> options {
+      { kOption0 },
+      { kOption1, kOption1Argument0 },
+      { kOption2, kOption2Argument0, kOption2Argument1 }
+  };
+  FilePath config_directory(
+      temporary_directory_.path().Append(kOpenVPNConfigDirectory));
+  FilePath config_file;
+  EXPECT_FALSE(file_util::PathExists(config_directory));
+  EXPECT_TRUE(driver_->WriteConfigFile(options, &config_file));
+  EXPECT_TRUE(file_util::PathExists(config_directory));
+  EXPECT_TRUE(file_util::PathExists(config_file));
+  EXPECT_TRUE(file_util::ContainsPath(config_directory, config_file));
+
+  string config_contents;
+  EXPECT_TRUE(file_util::ReadFileToString(config_file, &config_contents));
+  string expected_config_contents = StringPrintf(
+      "%s\n%s %s\n%s %s %s\n",
+      kOption0,
+      kOption1, kOption1Argument0,
+      kOption2, kOption2Argument0, kOption2Argument1);
+  EXPECT_EQ(expected_config_contents, config_contents);
+}
+
 }  // namespace shill
diff --git a/openvpn_management_server.cc b/openvpn_management_server.cc
index 507423e..d47db41 100644
--- a/openvpn_management_server.cc
+++ b/openvpn_management_server.cc
@@ -92,16 +92,16 @@
   dispatcher_ = dispatcher;
 
   // Append openvpn management API options.
-  driver_->AppendOption("--management", inet_ntoa(addr.sin_addr),
+  driver_->AppendOption("management", inet_ntoa(addr.sin_addr),
                         IntToString(ntohs(addr.sin_port)), options);
-  driver_->AppendOption("--management-client", options);
-  driver_->AppendOption("--management-hold", options);
+  driver_->AppendOption("management-client", options);
+  driver_->AppendOption("management-hold", options);
   hold_release_ = false;
   hold_waiting_ = false;
 
-  driver_->AppendOption("--management-query-passwords", options);
+  driver_->AppendOption("management-query-passwords", options);
   if (driver_->AppendValueOption(flimflam::kOpenVPNStaticChallengeProperty,
-                                 "--static-challenge",
+                                 "static-challenge",
                                  options)) {
     options->back().push_back("1");  // Force echo.
   }
diff --git a/openvpn_management_server_unittest.cc b/openvpn_management_server_unittest.cc
index 124e405..13723ca 100644
--- a/openvpn_management_server_unittest.cc
+++ b/openvpn_management_server_unittest.cc
@@ -195,11 +195,11 @@
   EXPECT_TRUE(server_.ready_handler_.get());
   EXPECT_EQ(&dispatcher_, server_.dispatcher_);
   vector<vector<string>> expected_options {
-      { "--management", "127.0.0.1", "0" },
-      { "--management-client" },
-      { "--management-hold" },
-      { "--management-query-passwords" },
-      { "--static-challenge", kStaticChallenge, "1" }
+      { "management", "127.0.0.1", "0" },
+      { "management-client" },
+      { "management-hold" },
+      { "management-query-passwords" },
+      { "static-challenge", kStaticChallenge, "1" }
   };
   EXPECT_EQ(expected_options, options);
 }