[network-manager-openvpn/th/ovpn-import-bgo761285: 7/13] properties: refactor connection import



commit 41caeb417eb5f949bca398d1db783a0c4b98697e
Author: Thomas Haller <thaller redhat com>
Date:   Thu Jan 28 12:48:55 2016 +0100

    properties: refactor connection import
    
    Refactor the import code completely.
    
    - openvpn supports quoting and escaping. The parsing code sometimes
      considered quotes (e.g. see unquote() function and the line
      strcmp (file, "'auto'")).
      However, we must consider escaping/quoting while splitting the
      line into invidivual args. get_args() did that wrong in that
      it right away called g_strsplit_set().
      Now, split the line right away with args_parse_line().
    
    - don't print any g_warning() during import but instead return
      a proper error. Import will be called from nmcli and it is bad
      that a library spams stdout/strerr.
      This makes import code much less forgiving. Now instead of printing
      a warning, import fails.
    
    - also, be more strict about the number of arguments that an option
      has. Now, if the number of arguments mismatches, fail with error.
    
    - optimally, we would like to fail for every option that we don't
      understand. It is questionable to signal success during import
      when certain parts are not understood. Anyway, that did not change
      yet, unrecognized options are still silently ignored. But we got
      stricter with arguments that we understand and where we now enforce
      the right number of arguments.
    
    - adjust the blob-reading code to be similar to what openvpn does.
      E.g. the end-tag is detected with allowing leading whitespace and
      and trailing message.
    
    - also, don't treat contents as a NUL terminated C string, but consider
      the length. Lines are split at either '\0' or '\n'. E.g. a inline blob
      element with '\0' will still be written out as read-in.

 .gitattributes                          |    2 +
 properties/import-export.c              | 1682 +++++++++++++++----------------
 properties/tests/conf/tls-inline-ca.pem |  Bin 1090 -> 1146 bytes
 properties/tests/conf/tls-inline-ta.pem |    3 +
 properties/tests/conf/tls-inline.ovpn   |  Bin 6642 -> 6643 bytes
 properties/tests/test-import-export.c   |   25 +-
 6 files changed, 824 insertions(+), 888 deletions(-)
---
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..ec61a9d
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,2 @@
+/properties/tests/conf/*.ovpn diff
+/properties/tests/conf/tls-inline-ca.pem diff
diff --git a/properties/import-export.c b/properties/import-export.c
index cc99d70..aef8135 100644
--- a/properties/import-export.c
+++ b/properties/import-export.c
@@ -40,83 +40,251 @@
 #include "utils.h"
 #include "nm-macros-internal.h"
 
-#define CA_BLOB_START_TAG "<ca>"
-#define CA_BLOB_END_TAG "</ca>"
-#define CERT_BLOB_START_TAG "<cert>"
-#define CERT_BLOB_END_TAG "</cert>"
-#define KEY_BLOB_START_TAG "<key>"
-#define KEY_BLOB_END_TAG "</key>"
-#define TLS_AUTH_BLOB_START_TAG "<tls-auth>"
-#define TLS_AUTH_BLOB_END_TAG "</tls-auth>"
-
-#define TAG_AUTH                        "auth "
+
+#define INLINE_BLOB_CA                  "ca"
+#define INLINE_BLOB_CERT                "cert"
+#define INLINE_BLOB_KEY                 "key"
+#define INLINE_BLOB_TLS_AUTH            "tls-auth"
+
+#define TAG_AUTH                        "auth"
 #define TAG_AUTH_USER_PASS              "auth-user-pass"
-#define TAG_CA                          "ca "
-#define TAG_CERT                        "cert "
-#define TAG_CIPHER                      "cipher "
+#define TAG_CA                          "ca"
+#define TAG_CERT                        "cert"
+#define TAG_CIPHER                      "cipher"
 #define TAG_CLIENT                      "client"
 #define TAG_COMP_LZO                    "comp-lzo"
-#define TAG_DEV                         "dev "
-#define TAG_DEV_TYPE                    "dev-type "
+#define TAG_DEV                         "dev"
+#define TAG_DEV_TYPE                    "dev-type"
 #define TAG_FLOAT                       "float"
-#define TAG_FRAGMENT                    "fragment "
-#define TAG_HTTP_PROXY                  "http-proxy "
+#define TAG_FRAGMENT                    "fragment"
+#define TAG_HTTP_PROXY                  "http-proxy"
 #define TAG_HTTP_PROXY_RETRY            "http-proxy-retry"
-#define TAG_IFCONFIG                    "ifconfig "
-#define TAG_KEEPALIVE                   "keepalive "
-#define TAG_KEY_DIRECTION               "key-direction "
-#define TAG_KEY                         "key "
-#define TAG_KEYSIZE                     "keysize "
+#define TAG_IFCONFIG                    "ifconfig"
+#define TAG_KEEPALIVE                   "keepalive"
+#define TAG_KEY_DIRECTION               "key-direction"
+#define TAG_KEY                         "key"
+#define TAG_KEYSIZE                     "keysize"
 #define TAG_MSSFIX                      "mssfix"
-#define TAG_PING_EXIT                   "ping-exit "
-#define TAG_PING                        "ping "
-#define TAG_PING_RESTART                "ping-restart "
-#define TAG_PKCS12                      "pkcs12 "
-#define TAG_PORT                        "port "
-#define TAG_PROTO                       "proto "
-#define TAG_REMOTE_CERT_TLS             "remote-cert-tls "
+#define TAG_NOBIND                      "nobind"
+#define TAG_PING_EXIT                   "ping-exit"
+#define TAG_PING                        "ping"
+#define TAG_PING_RESTART                "ping-restart"
+#define TAG_PKCS12                      "pkcs12"
+#define TAG_PORT                        "port"
+#define TAG_PROTO                       "proto"
+#define TAG_REMOTE_CERT_TLS             "remote-cert-tls"
 #define TAG_REMOTE_RANDOM               "remote-random"
-#define TAG_REMOTE                      "remote "
-#define TAG_RENEG_SEC                   "reneg-sec "
-#define TAG_ROUTE                       "route "
-#define TAG_RPORT                       "rport "
-#define TAG_SECRET                      "secret "
+#define TAG_REMOTE                      "remote"
+#define TAG_RENEG_SEC                   "reneg-sec"
+#define TAG_ROUTE                       "route"
+#define TAG_RPORT                       "rport"
+#define TAG_SECRET                      "secret"
 #define TAG_SOCKS_PROXY_RETRY           "socks-proxy-retry"
-#define TAG_SOCKS_PROXY                 "socks-proxy "
-#define TAG_TLS_AUTH                    "tls-auth "
+#define TAG_SOCKS_PROXY                 "socks-proxy"
+#define TAG_TLS_AUTH                    "tls-auth"
 #define TAG_TLS_CLIENT                  "tls-client"
-#define TAG_TLS_REMOTE                  "tls-remote "
-#define TAG_TUN_MTU                     "tun-mtu "
+#define TAG_TLS_REMOTE                  "tls-remote"
+#define TAG_TOPOLOGY                    "topology"
+#define TAG_TUN_MTU                     "tun-mtu"
 
 
-/**
- * args_is_option:
- * @line: the entire line from the config file
- * @tag: the option tag to match against. Optionally
- *   terminated by white space.
- *
- * Returns: %TRUE if @line starts with @tag.
- * */
+static void
+__attribute__((__format__ (__printf__, 3, 4)))
+setting_vpn_add_data_item_v (NMSettingVpn *setting,
+                             const char *key,
+                             const char *format,
+                             ...)
+{
+       char buf[256];
+       char *s;
+       int l;
+       va_list ap, ap2;
+
+       g_return_if_fail (NM_IS_SETTING_VPN (setting));
+
+       /* let's first try with a stack allocated buffer,
+        * it's large enough for most cases. */
+       va_start (ap, format);
+       va_copy (ap2, ap);
+       l = g_vsnprintf (buf, sizeof (buf), format, ap2);
+       va_end (ap2);
+
+       if (l < sizeof (buf) - 1) {
+               va_end (ap);
+               nm_setting_vpn_add_data_item (setting, key, buf);
+               return;
+       }
+
+       s = g_strdup_vprintf (format, ap);
+       va_end (ap);
+       nm_setting_vpn_add_data_item (setting, key, s);
+       g_free (s);
+}
+
 static gboolean
-args_is_option (const char *line, const char *tag)
+_streq (const char *s1, const char *s2)
 {
-       gsize len;
+       g_return_val_if_fail (s1, FALSE);
+       g_return_val_if_fail (s2, FALSE);
 
-       g_return_val_if_fail (line, FALSE);
-       g_return_val_if_fail (tag, FALSE);
-       g_return_val_if_fail (tag[0], FALSE);
+       return strcmp (s1, s2) == 0;
+}
+
+static gboolean
+_str_in_set_impl (const char *option, ...)
+{
+       va_list ap;
+       const char *tag;
+       gboolean found = FALSE;
+
+       g_return_val_if_fail (option, FALSE);
 
-       len = strlen (tag);
+       va_start (ap, option);
+       tag = va_arg (ap, const char *);
+       if (!tag)
+               g_return_val_if_reached (FALSE);
+       do {
+               if (strcmp (option, tag) == 0) {
+                       found = TRUE;
+                       break;
+               }
+       } while ((tag = va_arg (ap, const char *)));
+       va_end (ap);
 
-       /* allow the tag to be terminated by whitespace */
-       if (g_ascii_isspace (tag[len - 1]))
-               len--;
+       return found;
+}
+#define _str_in_set(option, ...) _str_in_set_impl (option, __VA_ARGS__, NULL)
 
-       if (strncmp (line, tag, len) != 0)
+static gboolean
+args_params_check_nargs_minmax (const char **params, guint nargs_min, guint nargs_max, char **out_error)
+{
+       guint nargs;
+
+       g_return_val_if_fail (params, FALSE);
+       g_return_val_if_fail (params[0], FALSE);
+       g_return_val_if_fail (out_error && !*out_error, FALSE);
+
+       nargs = g_strv_length ((char **) params) - 1;
+
+       if (nargs < nargs_min || nargs > nargs_max) {
+               if (nargs_min == nargs_max) {
+                       *out_error = g_strdup_printf (_("option %s expects %s argument%s"),
+                                                     params[0],
+                                                     nargs_min == 0
+                                                         ? "no"
+                                                         : (nargs_min == 1
+                                                                ? "exactly one"
+                                                                : nm_sprintf_bufa (100, "%u", nargs_min)),
+                                                     nargs_min == 1 ? "" : "s");
+               } else {
+                       *out_error = g_strdup_printf (_("option %s expects between %u and %u arguments"),
+                                                     params[0],
+                                                     nargs_min,
+                                                     nargs_max);
+               }
                return FALSE;
-       if (line[len] == '\0' || g_ascii_isspace (line[len]))
-               return TRUE;
-       return FALSE;
+       }
+       return TRUE;
+}
+
+static gboolean
+args_params_check_nargs_n (const char **params, guint nargs, char **out_error)
+{
+       return args_params_check_nargs_minmax (params, nargs, nargs, out_error);
+}
+
+static gboolean
+args_params_parse_int64 (const char **params,
+                         guint n_param,
+                         gint64 min,
+                         gint64 max,
+                         gint64 *out,
+                         char **out_error)
+{
+       gint64 v;
+
+       g_return_val_if_fail (params, FALSE);
+       g_return_val_if_fail (params[0], FALSE);
+       g_return_val_if_fail (n_param > 0, FALSE);
+       g_return_val_if_fail (n_param < g_strv_length ((char **) params), FALSE);
+       g_return_val_if_fail (out_error && !*out_error, FALSE);
+
+       v = _nm_utils_ascii_str_to_int64 (params[n_param], 10, min, max, -1);
+       if (errno) {
+               *out_error = g_strdup_printf (_("invalid %uth argument to '%s' where number expected"),
+                                             n_param,
+                                             params[0]);
+               return FALSE;
+       }
+       *out = v;
+       return TRUE;
+}
+
+static gboolean
+args_params_parse_port (const char **params, guint n_param, gint64 *out, char **out_error)
+{
+       return args_params_parse_int64 (params, n_param, 1, 65535, out, out_error);
+}
+
+static gboolean
+args_params_parse_ip4 (const char **params,
+                       guint n_param,
+                       in_addr_t *out,
+                       char **out_error)
+{
+       in_addr_t a;
+
+       g_return_val_if_fail (params, FALSE);
+       g_return_val_if_fail (params[0], FALSE);
+       g_return_val_if_fail (n_param > 0, FALSE);
+       g_return_val_if_fail (n_param < g_strv_length ((char **) params), FALSE);
+       g_return_val_if_fail (out, FALSE);
+       g_return_val_if_fail (out_error && !*out_error, FALSE);
+
+       if (inet_pton (AF_INET, params[n_param], &a) != 1) {
+               *out_error = g_strdup_printf (_("invalid %uth argument to '%s' where IPv4 address expected"),
+                                             n_param,
+                                             params[0]);
+               return FALSE;
+       }
+       *out = a;
+       return TRUE;
+}
+
+static gboolean
+args_params_parse_key_direction (const char **params,
+                                 guint n_param,
+                                 const char **out_key_direction,
+                                 char **out_error)
+{
+       g_return_val_if_fail (params, FALSE);
+       g_return_val_if_fail (params[0], FALSE);
+       g_return_val_if_fail (n_param > 0, FALSE);
+       g_return_val_if_fail (n_param < g_strv_length ((char **) params), FALSE);
+       g_return_val_if_fail (out_key_direction, FALSE);
+       g_return_val_if_fail (out_error && !*out_error, FALSE);
+
+       /* params will be freed in the next loop iteration. "internalize" the values. */
+       if (_streq (params[n_param], "0"))
+               *out_key_direction = "0";
+       else if (_streq (params[n_param], "1"))
+               *out_key_direction = "1";
+       else {
+               *out_error = g_strdup_printf (_("invalid %uth key-direction argument to '%s'"), n_param, 
params[0]);
+               return FALSE;
+       }
+       return TRUE;
+}
+
+static char *
+args_params_error_message_invalid_arg (const char **params, guint n_param)
+{
+       g_return_val_if_fail (params, NULL);
+       g_return_val_if_fail (params[0], NULL);
+       g_return_val_if_fail (n_param > 0, FALSE);
+       g_return_val_if_fail (n_param < g_strv_length ((char **) params), FALSE);
+
+       return g_strdup_printf (_("invalid %uth argument to '%s'"), n_param, params[0]);
 }
 
 static char
@@ -286,297 +454,86 @@ _nmovpn_test_args_parse_line (const char *line,
        return args_parse_line (line, line_len, out_p, out_error);
 }
 
-static char *
-unquote (const char *line, char **leftover)
-{
-       char *tmp, *item, *unquoted = NULL, *p;
-       gboolean quoted = FALSE;
-
-       if (leftover)
-               g_return_val_if_fail (*leftover == NULL, FALSE);
-
-       tmp = g_strdup (line);
-       item = g_strstrip (tmp);
-       if (!strlen (item)) {
-               g_free (tmp);
-               return NULL;
-       }
-
-       /* Simple unquote */
-       if ((item[0] == '"') || (item[0] == '\'')) {
-               quoted = TRUE;
-               item++;
-       }
-
-       /* Unquote stuff using openvpn unquoting rules */
-       unquoted = g_malloc0 (strlen (item) + 1);
-       for (p = unquoted; *item; item++, p++) {
-               if (quoted && ((*item == '"') || (*item == '\'')))
-                       break;
-               else if (!quoted && isspace (*item))
-                       break;
-
-               if (*item == '\\' && *(item+1) == '\\')
-                       *p = *(++item);
-               else if (*item == '\\' && *(item+1) == '"')
-                       *p = *(++item);
-               else if (*item == '\\' && *(item+1) == ' ')
-                       *p = *(++item);
-               else
-                       *p = *item;
-       }
-       if (leftover && *item)
-               *leftover = g_strdup (item + 1);
-
-       g_free (tmp);
-       return unquoted;
-}
-
-
 static gboolean
-handle_path_item (const char *line,
-                  const char *tag,
-                  const char *key,
-                  NMSettingVpn *s_vpn,
-                  const char *path,
-                  char **leftover)
-{
-       char *file, *full_path = NULL;
-
-       if (!args_is_option (line, tag))
-               return FALSE;
-
-       file = unquote (line + strlen (tag), leftover);
-       if (!file) {
-               if (leftover) {
-                       g_free (*leftover);
-                       leftover = NULL;
-               }
-               return FALSE;
-       }
-
-       /* If file isn't an absolute file name, add the default path */
-       if (!g_path_is_absolute (file))
-               full_path = g_build_filename (path, file, NULL);
-
-       nm_setting_vpn_add_data_item (s_vpn, key, full_path ? full_path : file);
-
-       g_free (file);
-       g_free (full_path);
-       return TRUE;
-}
-
-static void
-handle_direction (const char *tag, const char *key, char *leftover, NMSettingVpn *s_vpn);
-
-static gboolean
-handle_blob_item (const char ***line,
-                  const char *key,
-                  NMSettingVpn *s_vpn,
-                  const char *name,
-                  GError **error)
-{
-       gboolean success = FALSE;
-       const char *start_tag, *end_tag;
-       char *filename = NULL;
-       char *dirname = NULL;
-       char *path = NULL;
-       GString *in_file = NULL;
-       const char **p;
-
-#define NEXT_LINE \
-       G_STMT_START { \
-               do { \
-                       p++; \
-                       if (!*p) \
-                               goto finish; \
-               } while (*p[0] == '\0' || *p[0] == '#' || *p[0] == ';'); \
-       } G_STMT_END
-
-       if (!strcmp (key, NM_OPENVPN_KEY_CA)) {
-               start_tag = CA_BLOB_START_TAG;
-               end_tag = CA_BLOB_END_TAG;
-       } else if (!strcmp (key, NM_OPENVPN_KEY_CERT)) {
-               start_tag = CERT_BLOB_START_TAG;
-               end_tag = CERT_BLOB_END_TAG;
-       } else if (!strcmp (key, NM_OPENVPN_KEY_TA)) {
-               start_tag = TLS_AUTH_BLOB_START_TAG;
-               end_tag = TLS_AUTH_BLOB_END_TAG;
-       } else if (!strcmp (key, NM_OPENVPN_KEY_KEY)) {
-               start_tag = KEY_BLOB_START_TAG;
-               end_tag = KEY_BLOB_END_TAG;
-       } else
-               g_return_val_if_reached (FALSE);
-       p = *line;
-       if (strncmp (*p, start_tag, strlen (start_tag)))
-               goto finish;
-
-       NEXT_LINE;
-
-       in_file = g_string_new (NULL);
-
-       while (*p && strcmp (*p, end_tag)) {
-               g_string_append (in_file, *p);
-               g_string_append_c (in_file, '\n');
-               NEXT_LINE;
-       }
-
-       /* Construct file name to write the data in */
-       filename = g_strdup_printf ("%s-%s.pem", name, key);
-       dirname = g_build_filename (g_get_home_dir (), ".cert", NULL);
-       path = g_build_filename (dirname, filename, NULL);
-
-       /* Check that dirname exists and is a directory, otherwise create it */
-       if (!g_file_test (dirname, G_FILE_TEST_IS_DIR)) {
-               if (!g_file_test (dirname, G_FILE_TEST_EXISTS)) {
-                       if (mkdir (dirname, 0755) < 0)
-                               goto finish;  /* dirname could not be created */
-               } else
-                       goto finish;  /* dirname is not a directory */
-       }
-
-       /* Write the new file */
-       success = g_file_set_contents (path, in_file->str, -1, error);
-       if (!success)
-               goto finish;
-
-       nm_setting_vpn_add_data_item (s_vpn, key, path);
-finish:
-       *line = p;
-       g_free (filename);
-       g_free (dirname);
-       g_free (path);
-       if (in_file)
-               g_string_free (in_file, TRUE);
-
-       return success;
-
-}
-
-static char **
-get_args (const char *line, int *nitems)
+args_next_line (const char **content,
+                gsize *content_len,
+                const char **cur_line,
+                gsize *cur_line_len,
+                const char **cur_line_delimiter)
 {
-       char **split, **sanitized, **tmp, **tmp2;
+       const char *s;
+       gsize l, offset;
 
-       split = g_strsplit_set (line, " \t", 0);
-       sanitized = g_malloc0 (sizeof (char *) * (g_strv_length (split) + 1));
+       g_return_val_if_fail (content, FALSE);
+       g_return_val_if_fail (content_len, FALSE);
+       g_return_val_if_fail (cur_line, FALSE);
+       g_return_val_if_fail (cur_line_len, FALSE);
+       g_return_val_if_fail (cur_line_delimiter, FALSE);
 
-       for (tmp = split, tmp2 = sanitized; *tmp; tmp++) {
-               if (strlen (*tmp))
-                       *tmp2++ = g_strdup (*tmp);
-       }
+       l = *content_len;
 
-       g_strfreev (split);
-       *nitems = g_strv_length (sanitized);
+       if (l <= 0)
+               return FALSE;
 
-       return sanitized;
-}
+       *cur_line = s = *content;
 
-static void
-handle_direction (const char *tag, const char *key, char *leftover, NMSettingVpn *s_vpn)
-{
-       glong direction;
+       while (l > 0 && !NM_IN_SET (s[0], '\0', '\n'))
+               _ch_step_1 (&s, &l);
 
-       if (!leftover)
-               return;
+       offset = s - *content;
+       *cur_line_len = offset;
 
-       leftover = g_strstrip (leftover);
-       if (!strlen (leftover))
-               return;
-
-       errno = 0;
-       direction = strtol (leftover, NULL, 10);
-       if (errno == 0) {
-               if (direction == 0)
-                       nm_setting_vpn_add_data_item (s_vpn, key, "0");
-               else if (direction == 1)
-                       nm_setting_vpn_add_data_item (s_vpn, key, "1");
+       /* cur_line_delimiter will point to a (static) string
+        * containing the dropped character.
+        * Or NULL if we reached the end of content. */
+       if (l > 0) {
+               if (s[0] == '\0')
+                       *cur_line_delimiter = "\0";
+               else
+                       *cur_line_delimiter = "\n";
+               offset++;
        } else
-               g_warning ("%s: unknown %s direction '%s'", __func__, tag, leftover);
-}
-
-static char *
-parse_port (const char *str, const char *line)
-{
-       glong port;
+               *cur_line_delimiter = NULL;
 
-       errno = 0;
-       port = strtol (str, NULL, 10);
-       if ((errno == 0) && (port > 0) && (port < 65536))
-               return g_strdup_printf ("%d", (gint) port);
+       *content_len -= offset;
+       *content += offset;
 
-       g_warning ("%s: invalid remote port in option '%s'", __func__, line);
-       return NULL;
-}
-
-/* returns -1 in case of error */
-static int
-parse_seconds (const char *str, const char *line)
-{
-       glong secs;
-
-       errno = 0;
-       secs = strtol (str, NULL, 10);
-       if ((errno == 0) && (secs >= 0) && (secs <= G_MAXINT))
-               return (int) secs;
-
-       g_warning ("%s: invalid number of seconds in option '%s' - must be in [0, %d]", __func__, line, 
G_MAXINT);
-       return -1;
-}
-
-static gboolean
-parse_protocol (const char *str, const char *line, gboolean *is_tcp)
-{
-       if (!g_strcmp0 (str, "udp")) {
-               if (is_tcp)
-                       *is_tcp = FALSE;
-               return TRUE;
-       } else if (!g_strcmp0 (str, "tcp")) {
-               if (is_tcp)
-                       *is_tcp = TRUE;
-               return TRUE;
-       } else {
-               g_warning ("%s: invalid protocol in option '%s'", __func__, line);
-               return FALSE;
-       }
+       return TRUE;
 }
 
 static gboolean
-parse_http_proxy_auth (const char *path,
+parse_http_proxy_auth (const char *default_path,
                        const char *file,
                        char **out_user,
-                       char **out_pass)
+                       char **out_pass,
+                       char **out_error)
 {
-       char *contents = NULL, *abspath = NULL, *tmp;
-       GError *error = NULL;
+       gs_free char *file_free = NULL;
+       gs_free char *contents = NULL;
        char **lines, **iter;
 
-       g_return_val_if_fail (out_user != NULL, FALSE);
-       g_return_val_if_fail (out_pass != NULL, FALSE);
+       g_return_val_if_fail (out_user && !*out_user, FALSE);
+       g_return_val_if_fail (out_pass && !*out_pass, FALSE);
+       g_return_val_if_fail (out_error && !*out_error, FALSE);
 
-       if (!file || !strcmp (file, "stdin") || !strcmp (file, "auto") || !strcmp (file, "'auto'"))
+       if (!file ||
+           _str_in_set (file, "stdin", "auto"))
                return TRUE;
 
        if (!g_path_is_absolute (file)) {
-               tmp = g_path_get_dirname (path);
-               abspath = g_build_path ("/", tmp, file, NULL);
-               g_free (tmp);
-       } else
-               abspath = g_strdup (file);
+               file_free = g_build_path ("/", default_path, file, NULL);
+               file = file_free;
+       }
 
        /* Grab user/pass from authfile */
-       if (!g_file_get_contents (abspath, &contents, NULL, &error)) {
-               g_warning ("%s: unable to read HTTP proxy authfile '%s': (%d) %s",
-                          __func__, abspath, error ? error->code : -1,
-                          error && error->message ? error->message : "(unknown)");
-               g_clear_error (&error);
-               g_free (abspath);
+       if (!g_file_get_contents (file, &contents, NULL, NULL)) {
+               *out_error = g_strdup_printf (_("unable to read HTTP proxy auth file"));
                return FALSE;
        }
 
        lines = g_strsplit_set (contents, "\n\r", 0);
        for (iter = lines; iter && *iter; iter++) {
-               if (!strlen (*iter))
+               if ((*iter)[0] == '\0')
                        continue;
                if (!*out_user)
                        *out_user = g_strdup (g_strstrip (*iter));
@@ -585,87 +542,50 @@ parse_http_proxy_auth (const char *path,
                        break;
                }
        }
-       if (lines)
-               g_strfreev (lines);
-       g_free (contents);
-       g_free (abspath);
-
-       return *out_user && *out_pass;
-}
-
-static gboolean
-handle_num_seconds_item (const char *line,
-                         const char *tag,
-                         const char *key,
-                         NMSettingVpn *s_vpn)
-{
-       char **items = NULL;
-       int nitems;
-       int seconds;
-
-       if (!args_is_option (line, tag))
-               return FALSE;
-
-       items = get_args (line + strlen (tag), &nitems);
-       if (nitems == 1) {
-               seconds = parse_seconds (items[0], line);
-               if (seconds >= 0) {
-                       char *tmp;
-
-                       tmp = g_strdup_printf ("%d", seconds);
-                       nm_setting_vpn_add_data_item (s_vpn, key, tmp);
-                       g_free (tmp);
-               }
-       } else
-               g_warning ("%s: invalid number of arguments in option '%s', must be one integer", __func__, 
line);
-
-       g_strfreev (items);
-       return TRUE;
-}
-
-static gboolean
-parse_ip (const char *str, const char *line, guint32 *out_ip)
-{
-       struct in_addr ip;
+       g_strfreev (lines);
 
-       if (inet_pton (AF_INET, str, &ip) <= 0) {
-               g_warning ("%s: invalid IP '%s' in option '%s'", __func__, str, line);
+       if (!*out_user || !*out_pass) {
+               *out_error = g_strdup_printf (_("cannot read user/password from HTTP proxy auth file"));
+               g_clear_pointer (out_user, g_free);
+               g_clear_pointer (out_pass, g_free);
                return FALSE;
        }
-       if (out_ip)
-               *out_ip = ip.s_addr;
        return TRUE;
 }
 
 NMConnection *
 do_import (const char *path, const char *contents, gsize contents_len, GError **error)
 {
-       NMConnection *connection = NULL;
+       gs_unref_object NMConnection *connection_free = NULL;
+       NMConnection *connection;
        NMSettingConnection *s_con;
        NMSettingIPConfig *s_ip4;
        NMSettingVpn *s_vpn;
-       char *last_dot;
-       char **line, **lines = NULL;
+       const char *cur_line, *cur_line_delimiter;
+       gsize cur_line_len;
+       gsize contents_cur_line;
        gboolean have_client = FALSE, have_remote = FALSE;
        gboolean have_pass = FALSE, have_sk = FALSE;
        const char *ctype = NULL;
-       char *basename;
-       char *default_path, *tmp, *tmp2;
-       char *new_contents = NULL;
-       gboolean http_proxy = FALSE, socks_proxy = FALSE, proxy_set = FALSE;
-       int nitems;
-       char *last_seen_key_direction = NULL;
+       gs_free char *basename = NULL;
+       gs_free char *default_path = NULL;
+       char *tmp, *tmp2;
+       gs_free char *new_contents = NULL;
+       const char *last_seen_key_direction = NULL;
+       gboolean have_certs, have_ca;
+
+       g_return_val_if_fail (!error || !*error, NULL);
 
        connection = nm_simple_connection_new ();
+       connection_free = connection;
        s_con = NM_SETTING_CONNECTION (nm_setting_connection_new ());
        nm_connection_add_setting (connection, NM_SETTING (s_con));
        s_ip4 = NM_SETTING_IP_CONFIG (nm_setting_ip4_config_new ());
        nm_connection_add_setting (connection, NM_SETTING (s_ip4));
        g_object_set (s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL);
-
        s_vpn = NM_SETTING_VPN (nm_setting_vpn_new ());
-
        g_object_set (s_vpn, NM_SETTING_VPN_SERVICE_TYPE, NM_VPN_SERVICE_TYPE_OPENVPN, NULL);
+       nm_connection_add_setting (connection, NM_SETTING (s_vpn));
 
        /* Get the default path for ca, cert, key file, these files maybe
         * in same path with the configuration file */
@@ -680,9 +600,9 @@ do_import (const char *path, const char *contents, gsize contents_len, GError **
        }
 
        basename = g_path_get_basename (path);
-       last_dot = strrchr (basename, '.');
-       if (last_dot)
-               *last_dot = '\0';
+       tmp = strrchr (basename, '.');
+       if (tmp)
+               *tmp = '\0';
        g_object_set (s_con, NM_SETTING_CONNECTION_ID, basename, NULL);
 
        if (!g_utf8_validate (contents, contents_len, NULL)) {
@@ -707,644 +627,651 @@ do_import (const char *path, const char *contents, gsize contents_len, GError **
                contents_len -= 3;
        }
 
-       lines = g_strsplit_set (contents, "\r\n", 0);
-       if (g_strv_length (lines) <= 1) {
-               g_set_error_literal (error,
-                                    OPENVPN_EDITOR_PLUGIN_ERROR,
-                                    OPENVPN_EDITOR_PLUGIN_ERROR_FILE_NOT_READABLE,
-                                    _("not a valid OpenVPN configuration file"));
-               g_object_unref (connection);
-               connection = NULL;
-               goto out;
-       }
+       contents_cur_line = 0;
+       while (args_next_line (&contents,
+                              &contents_len,
+                              &cur_line,
+                              &cur_line_len,
+                              &cur_line_delimiter)) {
+               gs_free const char **params = NULL;
+               char *line_error = NULL;
+               gint64 v_int64;
 
-       for (line = lines; *line; line++) {
-               char *comment, **items = NULL, *leftover = NULL;
+               contents_cur_line++;
 
-               if ((comment = strchr (*line, '#')))
-                       *comment = '\0';
-               if ((comment = strchr (*line, ';')))
-                       *comment = '\0';
-               if (!strlen (*line))
+               if (!args_parse_line (cur_line, cur_line_len, &params, &line_error))
+                       goto handle_line_error;
+
+               if (!params) {
+                       /* empty line of comments. */
                        continue;
+               }
 
-               if (   args_is_option (*line, TAG_CLIENT)
-                   || args_is_option (*line, TAG_TLS_CLIENT)) {
+               g_assert (params[0]);
+
+               /* allow for a leading double-dash and skip over it (bypass_doubledash). */
+               if (g_str_has_prefix (params[0], "--"))
+                       params[0] = &params[0][2];
+
+               if (_str_in_set (params[0], TAG_CLIENT, TAG_TLS_CLIENT)) {
+                       if (!args_params_check_nargs_n (params, 0, &line_error))
+                               goto handle_line_error;
                        have_client = TRUE;
                        continue;
                }
 
-               if (args_is_option (*line, TAG_KEY_DIRECTION)) {
-                       last_seen_key_direction = *line + strlen (TAG_KEY_DIRECTION);
+               if (_str_in_set (params[0], TAG_KEY_DIRECTION)) {
+                       if (!args_params_check_nargs_n (params, 1, &line_error))
+                               goto handle_line_error;
+                       if (!args_params_parse_key_direction (params, 1, &last_seen_key_direction, 
&line_error))
+                               goto handle_line_error;
                        continue;
                }
 
-               if (args_is_option (*line, TAG_DEV)) {
-                       items = get_args (*line + strlen (TAG_DEV), &nitems);
-                       if (nitems == 1) {
-                               nm_setting_vpn_add_data_item (s_vpn, NM_OPENVPN_KEY_DEV, items[0]);
-                       } else
-                               g_warning ("%s: invalid number of arguments in option '%s'", __func__, *line);
-
-                       g_strfreev (items);
+               if (_str_in_set (params[0], TAG_DEV)) {
+                       if (!args_params_check_nargs_n (params, 1, &line_error))
+                               goto handle_line_error;
+                       if (params[1][0] == '\0') {
+                               line_error = args_params_error_message_invalid_arg (params, 1);
+                               goto handle_line_error;
+                       }
+                       nm_setting_vpn_add_data_item (s_vpn, NM_OPENVPN_KEY_DEV, params[1]);
                        continue;
                }
 
-               if (args_is_option (*line, TAG_DEV_TYPE)) {
-                       items = get_args (*line + strlen (TAG_DEV_TYPE), &nitems);
-                       if (nitems == 1) {
-                               if (!strcmp (items[0], "tun") || !strcmp (items[0], "tap"))
-                                       nm_setting_vpn_add_data_item (s_vpn, NM_OPENVPN_KEY_DEV_TYPE, 
items[0]);
-                               else
-                                       g_warning ("%s: unknown %s option '%s'", __func__, TAG_DEV_TYPE, 
*line);
-                       } else
-                               g_warning ("%s: invalid number of arguments in option '%s'", __func__, *line);
-
-                       g_strfreev (items);
+               if (_str_in_set (params[0], TAG_DEV_TYPE)) {
+                       if (!args_params_check_nargs_n (params, 1, &line_error))
+                               goto handle_line_error;
+                       if (!_str_in_set (params[1], "tun", "tap")) {
+                               line_error = args_params_error_message_invalid_arg (params, 1);
+                               goto handle_line_error;
+                       }
+                       nm_setting_vpn_add_data_item (s_vpn, NM_OPENVPN_KEY_DEV_TYPE, params[1]);
                        continue;
                }
 
-               if (args_is_option (*line, TAG_PROTO)) {
-                       items = get_args (*line + strlen (TAG_PROTO), &nitems);
-                       if (nitems == 1) {
-                               /* Valid parameters are "udp", "tcp-client" and "tcp-server".
-                                * 'tcp' isn't technically valid, but it used to be accepted so
-                                * we'll handle it here anyway.
-                                */
-                               if (!strcmp (items[0], "udp")) {
-                                       /* ignore; udp is default */
-                               } else if (   !strcmp (items[0], "tcp-client")
-                                          || !strcmp (items[0], "tcp-server")
-                                          || !strcmp (items[0], "tcp")) {
-                                       nm_setting_vpn_add_data_item (s_vpn, NM_OPENVPN_KEY_PROTO_TCP, "yes");
-                               } else
-                                       g_warning ("%s: unknown %s option '%s'", __func__, TAG_PROTO, *line);
-                       } else
-                               g_warning ("%s: invalid number of arguments in option '%s'", __func__, *line);
-
-                       g_strfreev (items);
+               if (_str_in_set (params[0], TAG_PROTO)) {
+                       if (!args_params_check_nargs_n (params, 1, &line_error))
+                               goto handle_line_error;
+                       /* Valid parameters are "udp", "tcp-client" and "tcp-server".
+                        * 'tcp' isn't technically valid, but it used to be accepted so
+                        * we'll handle it here anyway.
+                        */
+                       if (_streq (params[1], "udp")) {
+                               /* ignore; udp is default */
+                       } else if (_str_in_set (params[1], "tcp-client", "tcp-server", "tcp"))
+                               nm_setting_vpn_add_data_item (s_vpn, NM_OPENVPN_KEY_PROTO_TCP, "yes");
+                       else {
+                               line_error = args_params_error_message_invalid_arg (params, 1);
+                               goto handle_line_error;
+                       }
                        continue;
                }
 
-               if (args_is_option (*line, TAG_MSSFIX)) {
+               if (_str_in_set (params[0], TAG_MSSFIX)) {
+                       if (!args_params_check_nargs_minmax (params, 0, 1, &line_error))
+                               goto handle_line_error;
+                       /* TODO: handle the mssfix argument. */
                        nm_setting_vpn_add_data_item (s_vpn, NM_OPENVPN_KEY_MSSFIX, "yes");
                        continue;
                }
 
-               if (args_is_option (*line, TAG_TUN_MTU)) {
-                       items = get_args (*line + strlen (TAG_TUN_MTU), &nitems);
-                       if (nitems == 1) {
-                               glong secs;
-
-                               errno = 0;
-                               secs = strtol (items[0], NULL, 10);
-                               if ((errno == 0) && (secs >= 0) && (secs < 0xffff)) {
-                                       tmp = g_strdup_printf ("%d", (guint32) secs);
-                                       nm_setting_vpn_add_data_item (s_vpn, NM_OPENVPN_KEY_TUNNEL_MTU, tmp);
-                                       g_free (tmp);
-                               } else
-                                       g_warning ("%s: invalid size in option '%s'", __func__, *line);
-                       } else
-                               g_warning ("%s: invalid number of arguments in option '%s'", __func__, *line);
-
-                       g_strfreev (items);
+               if (_str_in_set (params[0], TAG_TUN_MTU)) {
+                       if (!args_params_check_nargs_n (params, 1, &line_error))
+                               goto handle_line_error;
+                       if (!args_params_parse_int64 (params, 1, 0, 0xffff, &v_int64, &line_error))
+                               goto handle_line_error;
+                       setting_vpn_add_data_item_v (s_vpn, NM_OPENVPN_KEY_TUNNEL_MTU, "%u", (unsigned) 
v_int64);
                        continue;
                }
 
-               if (args_is_option (*line, TAG_FRAGMENT)) {
-                       items = get_args (*line + strlen (TAG_FRAGMENT), &nitems);
-
-                       if (nitems == 1) {
-                               glong secs;
-
-                               errno = 0;
-                               secs = strtol (items[0], NULL, 10);
-                               if ((errno == 0) && (secs >= 0) && (secs < 0xffff)) {
-                                       tmp = g_strdup_printf ("%d", (guint32) secs);
-                                       nm_setting_vpn_add_data_item (s_vpn, NM_OPENVPN_KEY_FRAGMENT_SIZE, 
tmp);
-                                       g_free (tmp);
-                               } else
-                                       g_warning ("%s: invalid size in option '%s'", __func__, *line);
-                       } else
-                               g_warning ("%s: invalid number of arguments in option '%s'", __func__, *line);
-
-                       g_strfreev (items);
+               if (_str_in_set (params[0], TAG_FRAGMENT)) {
+                       if (!args_params_check_nargs_n (params, 1, &line_error))
+                               goto handle_line_error;
+                       if (!args_params_parse_int64 (params, 1, 0, 0xffff, &v_int64, &line_error))
+                               goto handle_line_error;
+                       setting_vpn_add_data_item_v (s_vpn, NM_OPENVPN_KEY_FRAGMENT_SIZE, "%u", (unsigned) 
v_int64);
                        continue;
                }
 
-               if (args_is_option (*line, TAG_COMP_LZO)) {
+               if (_str_in_set (params[0], TAG_COMP_LZO)) {
+                       if (!args_params_check_nargs_minmax (params, 0, 1, &line_error))
+                               goto handle_line_error;
+                       if (params[1]) {
+                               if (_streq (params[1], "no"))
+                                       continue;
+                               if (!_streq (params[1], "yes")) {
+                                       line_error = g_strdup_printf (_("unsupported comp-lzo argument"));
+                                       goto handle_line_error;
+                               }
+                       }
                        nm_setting_vpn_add_data_item (s_vpn, NM_OPENVPN_KEY_COMP_LZO, "yes");
                        continue;
                }
 
-               if (args_is_option (*line, TAG_FLOAT)) {
+               if (_str_in_set (params[0], TAG_FLOAT)) {
+                       if (!args_params_check_nargs_n (params, 0, &line_error))
+                               goto handle_line_error;
                        nm_setting_vpn_add_data_item (s_vpn, NM_OPENVPN_KEY_FLOAT, "yes");
                        continue;
                }
 
-               if (args_is_option (*line, TAG_RENEG_SEC)) {
-                       items = get_args (*line + strlen (TAG_RENEG_SEC), &nitems);
-
-                       if (nitems == 1) {
-                               glong secs;
-
-                               errno = 0;
-                               secs = strtol (items[0], NULL, 10);
-                               if ((errno == 0) && (secs >= 0) && (secs <= 604800)) {
-                                       tmp = g_strdup_printf ("%d", (guint32) secs);
-                                       nm_setting_vpn_add_data_item (s_vpn, NM_OPENVPN_KEY_RENEG_SECONDS, 
tmp);
-                                       g_free (tmp);
-                               } else
-                                       g_warning ("%s: invalid time length in option '%s'", __func__, *line);
-                       }
-                       g_strfreev (items);
+               if (_str_in_set (params[0], TAG_RENEG_SEC)) {
+                       if (!args_params_check_nargs_n (params, 1, &line_error))
+                               goto handle_line_error;
+                       if (!args_params_parse_int64 (params, 1, 0, 0xffff, &v_int64, &line_error))
+                               goto handle_line_error;
+                       setting_vpn_add_data_item_v (s_vpn, NM_OPENVPN_KEY_RENEG_SECONDS, "%u", (unsigned) 
v_int64);
                        continue;
                }
 
-               if (   args_is_option (*line, TAG_HTTP_PROXY_RETRY)
-                   || args_is_option (*line, TAG_SOCKS_PROXY_RETRY)) {
+               if (_str_in_set (params[0], TAG_HTTP_PROXY_RETRY, TAG_SOCKS_PROXY_RETRY)) {
+                       if (!args_params_check_nargs_n (params, 0, &line_error))
+                               goto handle_line_error;
                        nm_setting_vpn_add_data_item (s_vpn, NM_OPENVPN_KEY_PROXY_RETRY, "yes");
                        continue;
                }
 
-               http_proxy = args_is_option (*line, TAG_HTTP_PROXY);
-               socks_proxy = args_is_option (*line, TAG_SOCKS_PROXY);
-               if ((http_proxy || socks_proxy) && !proxy_set) {
-                       gboolean success = FALSE;
+               if (_str_in_set (params[0], TAG_HTTP_PROXY, TAG_SOCKS_PROXY)) {
                        const char *proxy_type = NULL;
+                       gs_free char *s_port = NULL;
+                       gs_free char *user = NULL;
+                       gs_free char *pass = NULL;
 
-                       if (http_proxy) {
-                               items = get_args (*line + strlen (TAG_HTTP_PROXY), &nitems);
+                       if (_str_in_set (params[0], TAG_HTTP_PROXY)) {
                                proxy_type = "http";
-                       } else if (socks_proxy) {
-                               items = get_args (*line + strlen (TAG_SOCKS_PROXY), &nitems);
+                               if (!args_params_check_nargs_minmax (params, 2, 4, &line_error))
+                                       goto handle_line_error;
+                       } else {
                                proxy_type = "socks";
+                               if (!args_params_check_nargs_minmax (params, 1, 3, &line_error))
+                                       goto handle_line_error;
                        }
 
-                       if (nitems >= 2) {
-                               glong port;
-                               char *s_port = NULL;
-                               char *user = NULL, *pass = NULL;
-
-                               success = TRUE;
-                               if (http_proxy && nitems >= 3)
-                                       success = parse_http_proxy_auth (path, items[2], &user, &pass);
-
-                               if (success) {
-                                       success = FALSE;
-                                       errno = 0;
-                                       port = strtol (items[1], NULL, 10);
-                                       if ((errno == 0) && (port > 0) && (port < 65536)) {
-                                               s_port = g_strdup_printf ("%d", (guint32) port);
-                                               success = TRUE;
-                                       }
-                               }
-
-                               if (success && proxy_type) {
-                                       nm_setting_vpn_add_data_item (s_vpn, NM_OPENVPN_KEY_PROXY_TYPE, 
proxy_type);
-
-                                       nm_setting_vpn_add_data_item (s_vpn, NM_OPENVPN_KEY_PROXY_SERVER, 
items[0]);
-                                       nm_setting_vpn_add_data_item (s_vpn, NM_OPENVPN_KEY_PROXY_PORT, 
s_port);
-                                       if (user)
-                                               nm_setting_vpn_add_data_item (s_vpn, 
NM_OPENVPN_KEY_HTTP_PROXY_USERNAME, user);
-                                       if (pass) {
-                                               nm_setting_vpn_add_secret (s_vpn, 
NM_OPENVPN_KEY_HTTP_PROXY_PASSWORD, pass);
-                                               nm_setting_set_secret_flags (NM_SETTING (s_vpn),
-                                                                            
NM_OPENVPN_KEY_HTTP_PROXY_PASSWORD,
-                                                                            
NM_SETTING_SECRET_FLAG_AGENT_OWNED,
-                                                                            NULL);
-                                       }
-                                       proxy_set = TRUE;
-                               }
-                               g_free (s_port);
-                               g_free (user);
-                               g_free (pass);
+                       if (params[1][0] == '\0') {
+                               line_error = g_strdup_printf (_("empty %s service argument"), proxy_type);
+                               goto handle_line_error;
                        }
 
-                       if (!success)
-                               g_warning ("%s: invalid proxy option '%s'", __func__, *line);
+                       if (params[2]) {
+                               if (!args_params_parse_port (params, 2, &v_int64, &line_error))
+                                       goto handle_line_error;
+                               s_port = g_strdup_printf ("%u", (unsigned) v_int64);
 
-                       g_strfreev (items);
-                       continue;
-               }
-
-               if (args_is_option (*line, TAG_REMOTE)) {
-                       items = get_args (*line + strlen (TAG_REMOTE), &nitems);
-                       if (nitems >= 1 && nitems <= 3) {
-                               gboolean ok = TRUE;
-                               tmp = NULL;
-
-                               if (nitems >= 2) {
-                                       tmp = parse_port (items[1], *line);
-                                       ok = tmp != NULL;
-                                       if (ok && nitems == 3)
-                                               ok = parse_protocol (items[2], *line, NULL);
+                               if (params[3]) {
+                                       if (!parse_http_proxy_auth (default_path, params[3], &user, &pass, 
&line_error))
+                                               goto handle_line_error;
                                }
-                               if (ok) {
-                                       const char *prev;
-                                       GString *new_remote = g_string_sized_new (64);
-
-                                       have_remote = TRUE;
-                                       prev = nm_setting_vpn_get_data_item (s_vpn, NM_OPENVPN_KEY_REMOTE);
-                                       if (prev) {
-                                               g_string_assign (new_remote, prev);
-                                               g_string_append (new_remote, ", ");
-                                       }
-                                       g_string_append (new_remote, items[0]);
-                                       if (nitems >= 2) {
-                                               g_string_append_c (new_remote, ':');
-                                               g_string_append (new_remote, tmp);
-                                       }
-                                       if (nitems == 3) {
-                                               g_string_append_c (new_remote, ':');
-                                               g_string_append (new_remote, items[2]);
-                                       }
-                                       nm_setting_vpn_add_data_item (s_vpn, NM_OPENVPN_KEY_REMOTE, 
new_remote->str);
-                                       g_string_free (new_remote, TRUE);
-                                       g_free (tmp);
-                               }
-                       } else
-                               g_warning ("%s: invalid number of arguments in option '%s'", __func__, *line);
+                       }
 
-                       g_strfreev (items);
-                       continue;
-               }
+                       nm_setting_vpn_add_data_item (s_vpn, NM_OPENVPN_KEY_PROXY_TYPE, proxy_type);
 
-               if (args_is_option (*line, TAG_REMOTE_RANDOM)) {
-                       nm_setting_vpn_add_data_item (s_vpn, NM_OPENVPN_KEY_REMOTE_RANDOM, "yes");
+                       nm_setting_vpn_add_data_item (s_vpn, NM_OPENVPN_KEY_PROXY_SERVER, params[1]);
+                       if (s_port)
+                               nm_setting_vpn_add_data_item (s_vpn, NM_OPENVPN_KEY_PROXY_PORT, s_port);
+                       if (user)
+                               nm_setting_vpn_add_data_item (s_vpn, NM_OPENVPN_KEY_HTTP_PROXY_USERNAME, 
user);
+                       if (pass) {
+                               nm_setting_vpn_add_secret (s_vpn, NM_OPENVPN_KEY_HTTP_PROXY_PASSWORD, pass);
+                               nm_setting_set_secret_flags (NM_SETTING (s_vpn),
+                                                            NM_OPENVPN_KEY_HTTP_PROXY_PASSWORD,
+                                                            NM_SETTING_SECRET_FLAG_AGENT_OWNED,
+                                                            NULL);
+                       }
                        continue;
                }
 
-               if (   args_is_option (*line, TAG_PORT)
-                   || args_is_option (*line, TAG_RPORT)) {
-                       if (args_is_option (*line, TAG_PORT))
-                               items = get_args (*line + strlen (TAG_PORT), &nitems);
-                       else if (args_is_option (*line, TAG_RPORT))
-                               items = get_args (*line + strlen (TAG_RPORT), &nitems);
-                       else
-                               g_assert_not_reached ();
+               if (_str_in_set (params[0], TAG_REMOTE)) {
+                       const char *prev;
+                       GString *new_remote;
+                       int port = -1;
 
-                       if (nitems == 1) {
-                               tmp = parse_port (items[0], *line);
-                               if (tmp) {
-                                       nm_setting_vpn_add_data_item (s_vpn, NM_OPENVPN_KEY_PORT, tmp);
-                                       g_free (tmp);
-                               }
-                       } else
-                               g_warning ("%s: invalid number of arguments in option '%s'", __func__, *line);
+                       if (!args_params_check_nargs_minmax (params, 1, 3, &line_error))
+                               goto handle_line_error;
 
-                       g_strfreev (items);
-                       continue;
-               }
+                       if (!params[1][0]) {
+                               line_error = g_strdup_printf (_("missing remote argument"));
+                               goto handle_line_error;
+                       }
+                       if (strchr (params[1], ' ')) {
+                               line_error = g_strdup_printf (_("remote cannot contain space"));
+                               goto handle_line_error;
+                       }
+                       if (strchr (params[1], ',')) {
+                               line_error = g_strdup_printf (_("remote cannot contain comma"));
+                               goto handle_line_error;
+                       }
 
-               if (handle_num_seconds_item (*line, TAG_PING, NM_OPENVPN_KEY_PING, s_vpn))
-                       continue;
+                       if (params[2]) {
+                               if (!args_params_parse_port (params, 2, &v_int64, &line_error))
+                                       goto handle_line_error;
+                               port = v_int64;
 
-               if (handle_num_seconds_item (*line, TAG_PING_EXIT, NM_OPENVPN_KEY_PING_EXIT, s_vpn))
-                       continue;
+                               if (params[3]) {
+                                       if (!_str_in_set (params[3], "udp", "tcp")) {
+                                               line_error = g_strdup_printf (_("remote expects protocol 
either udp or remote"));
+                                               goto handle_line_error;
+                                       }
+                               }
+                       }
 
-               if (handle_num_seconds_item (*line, TAG_PING_RESTART, NM_OPENVPN_KEY_PING_RESTART, s_vpn))
-                       continue;
+                       new_remote = g_string_sized_new (64);
 
-               if ( handle_path_item (*line, TAG_PKCS12, NM_OPENVPN_KEY_CA, s_vpn, default_path, NULL) &&
-                    handle_path_item (*line, TAG_PKCS12, NM_OPENVPN_KEY_CERT, s_vpn, default_path, NULL) &&
-                    handle_path_item (*line, TAG_PKCS12, NM_OPENVPN_KEY_KEY, s_vpn, default_path, NULL))
-                       continue;
+                       prev = nm_setting_vpn_get_data_item (s_vpn, NM_OPENVPN_KEY_REMOTE);
+                       if (prev) {
+                               g_string_assign (new_remote, prev);
+                               g_string_append (new_remote, ", ");
+                       }
+                       g_string_append (new_remote, params[1]);
+                       if (params[2]) {
+                               g_string_append_printf (new_remote, ":%d", port);
+                               if (params[3]) {
+                                       g_string_append_c (new_remote, ':');
+                                       g_string_append (new_remote, params[3]);
+                               }
+                       }
+                       nm_setting_vpn_add_data_item (s_vpn, NM_OPENVPN_KEY_REMOTE, new_remote->str);
+                       g_string_free (new_remote, TRUE);
 
-               if (handle_path_item (*line, TAG_CA, NM_OPENVPN_KEY_CA, s_vpn, default_path, NULL))
+                       have_remote = TRUE;
                        continue;
+               }
 
-               if (handle_path_item (*line, TAG_CERT, NM_OPENVPN_KEY_CERT, s_vpn, default_path, NULL))
+               if (_str_in_set (params[0], TAG_REMOTE_RANDOM)) {
+                       if (!args_params_check_nargs_n (params, 0, &line_error))
+                               goto handle_line_error;
+                       nm_setting_vpn_add_data_item (s_vpn, NM_OPENVPN_KEY_REMOTE_RANDOM, "yes");
                        continue;
+               }
 
-               if (handle_path_item (*line, TAG_KEY, NM_OPENVPN_KEY_KEY, s_vpn, default_path, NULL))
+               if (_str_in_set (params[0], TAG_PORT, TAG_RPORT)) {
+                       if (!args_params_check_nargs_n (params, 1, &line_error))
+                               goto handle_line_error;
+                       if (!args_params_parse_port (params, 1, &v_int64, &line_error))
+                               goto handle_line_error;
+                       setting_vpn_add_data_item_v (s_vpn, NM_OPENVPN_KEY_PORT, "%u", (unsigned) v_int64);
                        continue;
+               }
 
-               if (handle_blob_item ((const char ***)&line, NM_OPENVPN_KEY_CA, s_vpn, basename, NULL))
-                       continue;
+               if (_str_in_set (params[0], TAG_PING, TAG_PING_EXIT, TAG_PING_RESTART)) {
+                       const char *key = NULL;
 
-               if (handle_blob_item ((const char ***)&line, NM_OPENVPN_KEY_CERT, s_vpn, basename, NULL))
-                       continue;
+                       if (!args_params_check_nargs_n (params, 1, &line_error))
+                               goto handle_line_error;
+                       if (!args_params_parse_int64 (params, 1, 0, G_MAXINT, &v_int64, &line_error))
+                               goto handle_line_error;
 
-               if (handle_blob_item ((const char ***)&line, NM_OPENVPN_KEY_KEY, s_vpn, basename, NULL))
-                       continue;
+                       if (_str_in_set (params[0], TAG_PING))
+                               key = NM_OPENVPN_KEY_PING;
+                       else if (_str_in_set (params[0], TAG_PING_EXIT))
+                               key = NM_OPENVPN_KEY_PING_EXIT;
+                       else if (_str_in_set (params[0], TAG_PING_RESTART))
+                               key = NM_OPENVPN_KEY_PING_RESTART;
 
-               if (handle_blob_item ((const char ***)&line, NM_OPENVPN_KEY_TA, s_vpn, basename, NULL)) {
-                       handle_direction ("tls-auth",
-                                         NM_OPENVPN_KEY_TA_DIR,
-                                         last_seen_key_direction,
-                                         s_vpn);
+                       setting_vpn_add_data_item_v (s_vpn, key, "%u", (unsigned) v_int64);
                        continue;
                }
 
-               if (handle_path_item (*line, TAG_SECRET, NM_OPENVPN_KEY_STATIC_KEY,
-                                     s_vpn, default_path, &leftover)) {
-                       handle_direction ("secret",
-                                         NM_OPENVPN_KEY_STATIC_KEY_DIRECTION,
-                                         leftover,
-                                         s_vpn);
-                       g_free (leftover);
-                       have_sk = TRUE;
-                       continue;
-               }
+               if (_str_in_set (params[0],
+                                TAG_PKCS12,
+                                TAG_CA,
+                                TAG_CERT,
+                                TAG_KEY,
+                                TAG_SECRET,
+                                TAG_TLS_AUTH)) {
+                       const char *file;
+                       gs_free char *file_free = NULL;
+                       gboolean can_have_direction;
+                       const char *s_direction = NULL;
+
+                       can_have_direction = _str_in_set (params[0],
+                                                         TAG_SECRET,
+                                                         TAG_TLS_AUTH);
+
+                       if (!args_params_check_nargs_minmax (params, 1, can_have_direction ? 2 : 1, 
&line_error))
+                               goto handle_line_error;
+                       file = params[1];
+
+                       if (file[0] == '\0') {
+                               line_error = g_strdup_printf (_("missing certificate file name"));
+                               goto handle_line_error;
+                       }
 
-               if (handle_path_item (*line, TAG_TLS_AUTH, NM_OPENVPN_KEY_TA,
-                                     s_vpn, default_path, &leftover)) {
-                       handle_direction ("tls-auth",
-                                         NM_OPENVPN_KEY_TA_DIR,
-                                         leftover,
-                                         s_vpn);
-                       g_free (leftover);
+                       if (params[2]) {
+                               if (!args_params_parse_key_direction (params, 2, &s_direction, &line_error))
+                                       goto handle_line_error;
+                               last_seen_key_direction = s_direction;
+                       }
+
+                       if (!g_path_is_absolute (file))
+                               file = file_free = g_build_filename (default_path, file, NULL);
+
+                       if (_str_in_set (params[0], TAG_PKCS12)) {
+                               nm_setting_vpn_add_data_item (s_vpn, NM_OPENVPN_KEY_CA, file);
+                               nm_setting_vpn_add_data_item (s_vpn, NM_OPENVPN_KEY_CERT, file);
+                               nm_setting_vpn_add_data_item (s_vpn, NM_OPENVPN_KEY_KEY, file);
+                       } else if (_str_in_set (params[0], TAG_CA))
+                               nm_setting_vpn_add_data_item (s_vpn, NM_OPENVPN_KEY_CA, file);
+                       else if (_str_in_set (params[0], TAG_CERT))
+                               nm_setting_vpn_add_data_item (s_vpn, NM_OPENVPN_KEY_CERT, file);
+                       else if (_str_in_set (params[0], TAG_KEY))
+                               nm_setting_vpn_add_data_item (s_vpn, NM_OPENVPN_KEY_KEY, file);
+                       else if (_str_in_set (params[0], TAG_SECRET)) {
+                               nm_setting_vpn_add_data_item (s_vpn, NM_OPENVPN_KEY_STATIC_KEY, file);
+                               if (s_direction)
+                                       nm_setting_vpn_add_data_item (s_vpn, 
NM_OPENVPN_KEY_STATIC_KEY_DIRECTION, s_direction);
+                               have_sk = TRUE;
+                       } else if (_str_in_set (params[0], TAG_TLS_AUTH)) {
+                               nm_setting_vpn_add_data_item (s_vpn, NM_OPENVPN_KEY_TA, file);
+                               if (s_direction)
+                                       nm_setting_vpn_add_data_item (s_vpn, NM_OPENVPN_KEY_TA_DIR, 
s_direction);
+                       } else
+                               g_assert_not_reached ();
                        continue;
                }
 
-               if (args_is_option (*line, TAG_CIPHER)) {
-                       items = get_args (*line + strlen (TAG_CIPHER), &nitems);
-                       if (nitems == 1)
-                               nm_setting_vpn_add_data_item (s_vpn, NM_OPENVPN_KEY_CIPHER, items[0]);
-                       else
-                               g_warning ("%s: invalid number of arguments in option '%s'", __func__, *line);
-
-                       g_strfreev (items);
+               if (_str_in_set (params[0], TAG_CIPHER)) {
+                       if (!args_params_check_nargs_n (params, 1, &line_error))
+                               goto handle_line_error;
+                       nm_setting_vpn_add_data_item (s_vpn, NM_OPENVPN_KEY_CIPHER, params[1]);
                        continue;
                }
 
-               if (args_is_option (*line, TAG_KEEPALIVE)) {
-                       int ping_secs;
-                       int ping_restart_secs;
-
-                       items = get_args (*line + strlen (TAG_KEEPALIVE), &nitems);
-                       if (nitems == 2) {
-                               ping_secs = parse_seconds (items[0], *line);
-                               ping_restart_secs = parse_seconds (items[1], *line);
-
-                               if (ping_secs >= 0 && ping_restart_secs >= 0) {
-                                       tmp = g_strdup_printf ("%d", ping_secs);
-                                       tmp2 = g_strdup_printf ("%d", ping_restart_secs);
-
-                                       nm_setting_vpn_add_data_item (s_vpn, NM_OPENVPN_KEY_PING, tmp);
-                                       nm_setting_vpn_add_data_item (s_vpn, NM_OPENVPN_KEY_PING_RESTART, 
tmp2);
-
-                                       g_free (tmp);
-                                       g_free (tmp2);
-                               } else
-                                       g_warning ("%s: invalid arguments in option '%s', must be two 
integers", __func__, *line);
-                       } else
-                               g_warning ("%s: invalid number of arguments in option '%s', must be two 
integers", __func__, *line);
-
-                       g_strfreev (items);
+               if (_str_in_set (params[0], TAG_KEEPALIVE)) {
+                       gint64 v2;
+
+                       if (!args_params_check_nargs_n (params, 2, &line_error))
+                               goto handle_line_error;
+                       if (!args_params_parse_int64 (params, 1, 0, G_MAXINT, &v_int64, &line_error))
+                               goto handle_line_error;
+                       if (!args_params_parse_int64 (params, 2, 0, G_MAXINT, &v2, &line_error))
+                               goto handle_line_error;
+                       setting_vpn_add_data_item_v (s_vpn, NM_OPENVPN_KEY_PING, "%u", (unsigned) v_int64);
+                       setting_vpn_add_data_item_v (s_vpn, NM_OPENVPN_KEY_PING_RESTART, "%u", (unsigned) v2);
                        continue;
                }
 
-               if (args_is_option (*line, TAG_KEYSIZE)) {
-                       items = get_args (*line + strlen (TAG_KEYSIZE), &nitems);
-                       if (nitems == 1) {
-                               glong key_size;
-
-                               errno = 0;
-                               key_size = strtol (items[0], NULL, 10);
-                               if ((errno == 0) && (key_size > 0) && (key_size <= 65535)) {
-                                       tmp = g_strdup_printf ("%d", (guint32) key_size);
-                                       nm_setting_vpn_add_data_item (s_vpn, NM_OPENVPN_KEY_KEYSIZE, tmp);
-                                       g_free (tmp);
-                               } else
-                                       g_warning ("%s: invalid key size in option '%s'", __func__, *line);
-                       } else
-                               g_warning ("%s: invalid number of arguments in option '%s'", __func__, *line);
-                       g_strfreev (items);
+               if (_str_in_set (params[0], TAG_KEYSIZE)) {
+                       if (!args_params_check_nargs_n (params, 1, &line_error))
+                               goto handle_line_error;
+                       if (!args_params_parse_int64 (params, 1, 1, 65535, &v_int64, &line_error))
+                               goto handle_line_error;
+                       setting_vpn_add_data_item_v (s_vpn, NM_OPENVPN_KEY_KEYSIZE, "%u", (unsigned) v_int64);
                        continue;
                }
 
-               if (args_is_option (*line, TAG_TLS_REMOTE)) {
-                       char *unquoted = unquote (*line + strlen (TAG_TLS_REMOTE), NULL);
-
-                       if (unquoted) {
-                               nm_setting_vpn_add_data_item (s_vpn, NM_OPENVPN_KEY_TLS_REMOTE, unquoted);
-                               g_free (unquoted);
-                       } else
-                               g_warning ("%s: unknown %s option '%s'", __func__, TAG_TLS_REMOTE, *line);
-
+               if (_str_in_set (params[0], TAG_TLS_REMOTE)) {
+                       if (!args_params_check_nargs_n (params, 1, &line_error))
+                               goto handle_line_error;
+                       if (params[1][0] == '\0') {
+                               line_error = g_strdup_printf (_("empty tls-remote argument"));
+                               goto handle_line_error;
+                       }
+                       nm_setting_vpn_add_data_item (s_vpn, NM_OPENVPN_KEY_TLS_REMOTE, params[1]);
                        continue;
                }
 
-               if (args_is_option (*line, TAG_REMOTE_CERT_TLS)) {
-                       items = get_args (*line + strlen (TAG_REMOTE_CERT_TLS), &nitems);
-                       if (nitems == 1) {
-                               if (   !strcmp (items[0], NM_OPENVPN_REM_CERT_TLS_CLIENT)
-                                   || !strcmp (items[0], NM_OPENVPN_REM_CERT_TLS_SERVER)) {
-                                       nm_setting_vpn_add_data_item (s_vpn, NM_OPENVPN_KEY_REMOTE_CERT_TLS, 
items[0]);
-                               } else
-                                       g_warning ("%s: unknown %s option '%s'", __func__, 
TAG_REMOTE_CERT_TLS, *line);
+               if (_str_in_set (params[0], TAG_REMOTE_CERT_TLS)) {
+                       if (!args_params_check_nargs_n (params, 1, &line_error))
+                               goto handle_line_error;
+                       if (!_str_in_set (params[1], NM_OPENVPN_REM_CERT_TLS_CLIENT, 
NM_OPENVPN_REM_CERT_TLS_SERVER)) {
+                               line_error = g_strdup_printf (_("invalid option"));
+                               goto handle_line_error;
                        }
-
-                       g_strfreev (items);
+                       nm_setting_vpn_add_data_item (s_vpn, NM_OPENVPN_KEY_REMOTE_CERT_TLS, params[1]);
                        continue;
                }
 
-               if (args_is_option (*line, TAG_IFCONFIG)) {
-                       items = get_args (*line + strlen (TAG_IFCONFIG), &nitems);
-                       if (nitems == 2) {
-                               nm_setting_vpn_add_data_item (s_vpn, NM_OPENVPN_KEY_LOCAL_IP, items[0]);
-                               nm_setting_vpn_add_data_item (s_vpn, NM_OPENVPN_KEY_REMOTE_IP, items[1]);
-                       } else
-                               g_warning ("%s: invalid number of arguments in option '%s'", __func__, *line);
-
-                       g_strfreev (items);
+               if (_str_in_set (params[0], TAG_IFCONFIG)) {
+                       if (!args_params_check_nargs_n (params, 2, &line_error))
+                               goto handle_line_error;
+                       nm_setting_vpn_add_data_item (s_vpn, NM_OPENVPN_KEY_LOCAL_IP, params[1]);
+                       nm_setting_vpn_add_data_item (s_vpn, NM_OPENVPN_KEY_REMOTE_IP, params[2]);
                        continue;
                }
 
-               if (args_is_option (*line, TAG_AUTH_USER_PASS)) {
+               if (_str_in_set (params[0], TAG_AUTH_USER_PASS)) {
+                       if (!args_params_check_nargs_minmax (params, 0, 1, &line_error))
+                               goto handle_line_error;
                        have_pass = TRUE;
                        continue;
                }
 
-               if (args_is_option (*line, TAG_AUTH)) {
-                       items = get_args (*line + strlen (TAG_AUTH), &nitems);
-                       if (nitems == 1)
-                               nm_setting_vpn_add_data_item (s_vpn, NM_OPENVPN_KEY_AUTH, items[0]);
-                       else
-                               g_warning ("%s: invalid number of arguments in option '%s'", __func__, *line);
-                       g_strfreev (items);
+               if (_str_in_set (params[0], TAG_AUTH)) {
+                       if (!args_params_check_nargs_n (params, 1, &line_error))
+                               goto handle_line_error;
+                       nm_setting_vpn_add_data_item (s_vpn, NM_OPENVPN_KEY_AUTH, params[1]);
                        continue;
                }
 
-#ifdef NM_OPENVPN_OLD
-               if (args_is_option (*line, TAG_ROUTE)) {
-                       items = get_args (*line + strlen (TAG_ROUTE), &nitems);
-                       if (nitems >= 1 && nitems <= 4) {
-                               guint32 dest, next_hop, prefix, metric;
-                               NMIP4Route *route;
+               if (_str_in_set (params[0], TAG_ROUTE)) {
+                       in_addr_t network;
+                       in_addr_t gateway = 0;
+                       guint32 prefix = 32;
+                       gint64 metric = -1;
 
-                               if (!parse_ip (items[0], *line, &dest))
-                                       goto route_fail;
-
-                               /* init default values */
-                               next_hop = 0;
-                               prefix = 32;
-                               metric = 0;
-                               if (nitems >= 2) {
-                                       if (!parse_ip (items[1], *line, &prefix))
-                                               goto route_fail;
-                                       prefix = nm_utils_ip4_netmask_to_prefix (prefix);
-                                       if (nitems >= 3) {
-                                               if (!parse_ip (items[2], *line, &next_hop))
-                                                       goto route_fail;
-                                               if (nitems == 4) {
-                                                       long num;
-                                                       errno = 0;
-                                                       num = strtol (items[3], NULL, 10);
-                                                       if ((errno == 0) && (num >= 0) && (num <= 65535))
-                                                               metric = (guint32) num;
-                                                       else {
-                                                               g_warning ("%s: invalid metric '%s' in option 
'%s'",
-                                                                          __func__, items[3], *line);
-                                                               goto route_fail;
-                                                       }
-                                               }
+                       if (!args_params_check_nargs_minmax (params, 1, 4, &line_error))
+                               goto handle_line_error;
+
+                       if (!args_params_parse_ip4 (params, 1, &network, &line_error))
+                               goto handle_line_error;
+
+                       if (params[2]) {
+                               in_addr_t netmask;
+
+                               if (!args_params_parse_ip4 (params, 2, &netmask, &line_error))
+                                       goto handle_line_error;
+                               prefix = nm_utils_ip4_netmask_to_prefix (netmask);
+
+                               if (params[3]) {
+                                       if (!args_params_parse_ip4 (params, 3, &gateway, &line_error))
+                                               goto handle_line_error;
+                                       if (params[4]) {
+                                               if (!args_params_parse_int64 (params, 4, 0, G_MAXUINT32, 
&v_int64, &line_error))
+                                                       goto handle_line_error;
+                                               metric = (guint32) v_int64;
                                        }
                                }
+                       }
+
+                       {
+#ifdef NM_OPENVPN_OLD
+                               NMIP4Route *route;
 
                                route = nm_ip4_route_new ();
-                               nm_ip4_route_set_dest (route, dest);
+                               nm_ip4_route_set_dest (route, network);
                                nm_ip4_route_set_prefix (route, prefix);
-                               nm_ip4_route_set_next_hop (route, next_hop);
-                               nm_ip4_route_set_metric (route, metric);
+                               nm_ip4_route_set_next_hop (route, gateway);
+                               if (metric >= 0)
+                                       nm_ip4_route_set_metric (route, metric);
                                nm_setting_ip4_config_add_route (s_ip4, route);
                                nm_ip4_route_unref (route);
-                       } else
-                               g_warning ("%s: invalid number of arguments in option '%s'", __func__, *line);
-
-route_fail:
-                       g_strfreev (items);
-                       continue;
-               }
 #else
-               if (args_is_option (*line, TAG_ROUTE)) {
-                       items = get_args (*line + strlen (TAG_ROUTE), &nitems);
-                       if (nitems >= 1 && nitems <= 4) {
-                               guint32 prefix = 32;
-                               guint32 metric = 0;
-                               const char *dest = items[0];
-                               const char *next_hop = "0.0.0.0";
                                NMIPRoute *route;
 
-                               if (!parse_ip (items[0], *line, NULL))
-                                       goto route_fail;
-
-                               if (nitems >= 2) {
-                                       if (!parse_ip (items[1], *line, &prefix))
-                                               goto route_fail;
-                                       prefix = nm_utils_ip4_netmask_to_prefix (prefix);
-                                       if (nitems >= 3) {
-                                               if (!parse_ip (items[2], *line, NULL))
-                                                       goto route_fail;
-                                               next_hop = items[2];
-                                               if (nitems == 4) {
-                                                       long num;
-                                                       errno = 0;
-                                                       num = strtol (items[3], NULL, 10);
-                                                       if ((errno == 0) && (num >= 0) && (num <= 65535))
-                                                               metric = (guint32) num;
-                                                       else {
-                                                               g_warning ("%s: invalid metric '%s' in option 
'%s'",
-                                                                          __func__, items[3], *line);
-                                                               goto route_fail;
-                                                       }
-                                               }
+                               route = nm_ip_route_new_binary (AF_INET, &network, prefix, params[3] ? 
&gateway : NULL, metric, NULL);
+                               nm_setting_ip_config_add_route (s_ip4, route);
+                               nm_ip_route_unref (route);
+#endif
+                       }
+               }
+
+               if (params[0][0] == '<' && params[0][strlen (params[0]) - 1] == '>') {
+                       gs_free char *token = g_strndup (&params[0][1], strlen (params[0]) - 2);
+                       gs_free char *end_token = NULL;
+                       gsize end_token_len;
+                       gsize my_contents_cur_line = contents_cur_line;
+                       gs_free char *f_filename = NULL;
+                       gs_free char *f_dirname = NULL;
+                       gs_free char *f_path = NULL;
+                       const char *key;
+                       gboolean can_have_direction = FALSE;
+                       GString *blob_data;
+
+                       if (_streq (token, INLINE_BLOB_CA))
+                               key = NM_OPENVPN_KEY_CA;
+                       else if (_streq (token, INLINE_BLOB_CERT))
+                               key = NM_OPENVPN_KEY_CERT;
+                       else if (_streq (token, INLINE_BLOB_KEY))
+                               key = NM_OPENVPN_KEY_KEY;
+                       else if (_streq (token, INLINE_BLOB_TLS_AUTH)) {
+                               key = NM_OPENVPN_KEY_TA;
+                               can_have_direction = TRUE;
+                       } else {
+                               line_error = g_strdup_printf (_("unsupported blob/xml element"));
+                               goto handle_line_error;
+                       }
+
+                       end_token = g_strdup_printf ("</%s>", token);
+                       end_token_len = strlen (end_token);
+
+                       blob_data = g_string_new (NULL);
+
+                       while (args_next_line (&contents,
+                                              &contents_len,
+                                              &cur_line,
+                                              &cur_line_len,
+                                              &cur_line_delimiter)) {
+                               my_contents_cur_line++;
+
+                               /* skip over trailing space like openvpn does. */
+                               _ch_skip_over_leading_whitespace (&cur_line, &cur_line_len);
+
+                               if (!strncmp (cur_line, end_token, end_token_len)) {
+                                       end_token_len = 0;
+                                       break;
+                               }
+
+                               g_string_append_len (blob_data, cur_line, cur_line_len);
+                               if (cur_line_delimiter)
+                                       g_string_append_c (blob_data, cur_line_delimiter[0]);
+                       }
+                       if (end_token_len) {
+                               line_error = g_strdup_printf (_("unterminated blob element <%s>"), token);
+                               g_string_free (blob_data, TRUE);
+                               goto handle_line_error;
+                       }
+                       contents_cur_line = my_contents_cur_line;
+
+                       /* Construct file name to write the data in */
+                       f_filename = g_strdup_printf ("%s-%s.pem", basename, token);
+                       f_dirname = g_build_filename (g_get_home_dir (), ".cert", NULL);
+                       f_path = g_build_filename (f_dirname, f_filename, NULL);
+
+                       /* Check that dirname exists and is a directory, otherwise create it */
+                       if (!g_file_test (f_dirname, G_FILE_TEST_IS_DIR)) {
+                               if (!g_file_test (f_dirname, G_FILE_TEST_EXISTS)) {
+                                       if (mkdir (f_dirname, 0755) < 0) {
+                                               line_error = g_strdup_printf (_("cannot create .cert 
directory for %s blob"), token);
+                                               goto handle_line_error;
                                        }
+                               } else {
+                                       line_error = g_strdup_printf (_(".cert directory is not usable for %s 
blob"), token);
+                                       goto handle_line_error;
                                }
+                       }
 
-                               route = nm_ip_route_new (AF_INET, dest, prefix, next_hop, metric, NULL);
-                               nm_setting_ip_config_add_route (s_ip4, route);
-                               nm_ip_route_unref (route);
-                       } else
-                               g_warning ("%s: invalid number of arguments in option '%s'", __func__, *line);
+                       /* Write the new file */
+                       if (!g_file_set_contents (f_path, blob_data->str, blob_data->len, error)) {
+                               line_error = g_strdup_printf (_("error writing %s blob to file"), token);
+                               goto handle_line_error;
+                       }
+
+                       g_string_free (blob_data, TRUE);
 
-route_fail:
-                       g_strfreev (items);
+                       nm_setting_vpn_add_data_item (s_vpn, key, f_path);
+                       if (   can_have_direction
+                           && last_seen_key_direction)
+                               nm_setting_vpn_add_data_item (s_vpn, NM_OPENVPN_KEY_TA_DIR, 
last_seen_key_direction);
                        continue;
                }
-#endif
 
+               /* TODO: currently we ignore any unknown options and skip over them. */
+               continue;
+
+handle_line_error:
+               g_set_error (error,
+                            OPENVPN_EDITOR_PLUGIN_ERROR,
+                            OPENVPN_EDITOR_PLUGIN_ERROR_FILE_INVALID,
+                            _("configuration error: %s (line %ld)"),
+                            line_error ? : _("unknown or unsupported option"),
+                            (long) contents_cur_line);
+               g_free (line_error);
+               goto out_error;
        }
 
        if (!have_client && !have_sk) {
                g_set_error_literal (error,
                                     OPENVPN_EDITOR_PLUGIN_ERROR,
                                     OPENVPN_EDITOR_PLUGIN_ERROR_FILE_NOT_OPENVPN,
-                                    _("The file to import wasn't a valid OpenVPN client configuration."));
-               g_object_unref (connection);
-               connection = NULL;
-       } else if (!have_remote) {
+                                    _("The file to import wasn't a valid OpenVPN client configuration"));
+               goto out_error;
+       }
+
+       if (!have_remote) {
                g_set_error_literal (error,
                                     OPENVPN_EDITOR_PLUGIN_ERROR,
                                     OPENVPN_EDITOR_PLUGIN_ERROR_FILE_NOT_OPENVPN,
-                                    _("The file to import wasn't a valid OpenVPN configure (no remote)."));
-               g_object_unref (connection);
-               connection = NULL;
-       } else {
-               gboolean have_certs = FALSE, have_ca = FALSE;
-
-               if (nm_setting_vpn_get_data_item (s_vpn, NM_OPENVPN_KEY_CA))
-                       have_ca = TRUE;
-
-               if (   have_ca
-                   && nm_setting_vpn_get_data_item (s_vpn, NM_OPENVPN_KEY_CERT)
-                   && nm_setting_vpn_get_data_item (s_vpn, NM_OPENVPN_KEY_KEY))
-                       have_certs = TRUE;
-
-               /* Determine connection type */
-               if (have_pass) {
-                       if (have_certs)
-                               ctype = NM_OPENVPN_CONTYPE_PASSWORD_TLS;
-                       else if (have_ca)
-                               ctype = NM_OPENVPN_CONTYPE_PASSWORD;
-               } else if (have_certs) {
-                       ctype = NM_OPENVPN_CONTYPE_TLS;
-               } else if (have_sk)
-                       ctype = NM_OPENVPN_CONTYPE_STATIC_KEY;
-
-               if (!ctype)
-                       ctype = NM_OPENVPN_CONTYPE_TLS;
-
-               nm_setting_vpn_add_data_item (s_vpn, NM_OPENVPN_KEY_CONNECTION_TYPE, ctype);
-
-               /* Default secret flags to be agent-owned */
-               if (have_pass) {
+                                    _("The file to import wasn't a valid OpenVPN configure (no remote)"));
+               goto out_error;
+       }
+
+       have_certs = FALSE;
+       have_ca = FALSE;
+
+       if (nm_setting_vpn_get_data_item (s_vpn, NM_OPENVPN_KEY_CA))
+               have_ca = TRUE;
+
+       if (   have_ca
+           && nm_setting_vpn_get_data_item (s_vpn, NM_OPENVPN_KEY_CERT)
+           && nm_setting_vpn_get_data_item (s_vpn, NM_OPENVPN_KEY_KEY))
+               have_certs = TRUE;
+
+       /* Determine connection type */
+       if (have_pass) {
+               if (have_certs)
+                       ctype = NM_OPENVPN_CONTYPE_PASSWORD_TLS;
+               else if (have_ca)
+                       ctype = NM_OPENVPN_CONTYPE_PASSWORD;
+       } else if (have_certs) {
+               ctype = NM_OPENVPN_CONTYPE_TLS;
+       } else if (have_sk)
+               ctype = NM_OPENVPN_CONTYPE_STATIC_KEY;
+
+       if (!ctype)
+               ctype = NM_OPENVPN_CONTYPE_TLS;
+
+       nm_setting_vpn_add_data_item (s_vpn, NM_OPENVPN_KEY_CONNECTION_TYPE, ctype);
+
+       /* Default secret flags to be agent-owned */
+       if (have_pass) {
+               nm_setting_set_secret_flags (NM_SETTING (s_vpn),
+                                            NM_OPENVPN_KEY_PASSWORD,
+                                            NM_SETTING_SECRET_FLAG_AGENT_OWNED,
+                                            NULL);
+       }
+       if (have_certs) {
+               const char *key_path;
+
+               key_path = nm_setting_vpn_get_data_item (s_vpn, NM_OPENVPN_KEY_KEY);
+               if (key_path && is_encrypted (key_path)) {
+                       /* If there should be a private key password, default it to
+                        * being agent-owned.
+                        */
                        nm_setting_set_secret_flags (NM_SETTING (s_vpn),
-                                                    NM_OPENVPN_KEY_PASSWORD,
+                                                    NM_OPENVPN_KEY_CERTPASS,
                                                     NM_SETTING_SECRET_FLAG_AGENT_OWNED,
                                                     NULL);
                }
-               if (have_certs) {
-                       const char *key_path;
-
-                       key_path = nm_setting_vpn_get_data_item (s_vpn, NM_OPENVPN_KEY_KEY);
-                       if (key_path && is_encrypted (key_path)) {
-                               /* If there should be a private key password, default it to
-                                * being agent-owned.
-                                */
-                               nm_setting_set_secret_flags (NM_SETTING (s_vpn),
-                                                            NM_OPENVPN_KEY_CERTPASS,
-                                                            NM_SETTING_SECRET_FLAG_AGENT_OWNED,
-                                                            NULL);
-                       }
-               }
        }
 
-out:
-       g_free (default_path);
-       g_free (basename);
-
-       if (connection)
-               nm_connection_add_setting (connection, NM_SETTING (s_vpn));
-       else if (s_vpn)
-               g_object_unref (s_vpn);
-
-       g_free (new_contents);
-       g_strfreev (lines);
-
+       connection_free = NULL;
+       g_return_val_if_fail (!error || !*error, connection);
        return connection;
+
+out_error:
+       g_return_val_if_fail (!error || *error, NULL);
+       return NULL;
 }
 
 gboolean
@@ -1726,6 +1653,7 @@ do_export (const char *path, NMConnection *connection, GError **error)
                        in_addr_t netmask;
                        guint prefix;
                        guint64 metric;
+                       char metric_buf[50];
 
 #ifdef NM_OPENVPN_OLD
                        char next_hop_str_buf[INET_ADDRSTRLEN] = { 0 };
@@ -1750,17 +1678,15 @@ do_export (const char *path, NMConnection *connection, GError **error)
                        next_hop_str = nm_ip_route_get_next_hop (route) ? : "0.0.0.0",
                        prefix = nm_ip_route_get_prefix (route);
                        metric = nm_ip_route_get_metric (route);
-                       if (metric == -1)
-                               metric = 50;
 #endif
                        netmask = nm_utils_ip4_prefix_to_netmask (prefix);
                        inet_ntop (AF_INET, (const void *) &netmask, netmask_str, sizeof (netmask_str));
 
-                       fprintf (f, "route %s %s %s %ld\n",
+                       fprintf (f, "route %s %s %s%s\n",
                                 dest_str,
                                 netmask_str,
                                 next_hop_str,
-                                (long) metric);
+                                metric == -1 ? "" : nm_sprintf_buf (metric_buf, " %u", (unsigned) metric));
                }
        }
 
diff --git a/properties/tests/conf/tls-inline-ca.pem b/properties/tests/conf/tls-inline-ca.pem
index f051ebe..e0c2eb7 100644
Binary files a/properties/tests/conf/tls-inline-ca.pem and b/properties/tests/conf/tls-inline-ca.pem differ
diff --git a/properties/tests/conf/tls-inline-ta.pem b/properties/tests/conf/tls-inline-ta.pem
index f13ac58..8762c36 100644
--- a/properties/tests/conf/tls-inline-ta.pem
+++ b/properties/tests/conf/tls-inline-ta.pem
@@ -1,3 +1,6 @@
+#
+# 2048 bit OpenVPN static key
+#
 -----BEGIN OpenVPN Static key V1-----
 378e209d0fe8efe398afa86bdb19b9a8
 f9cc3ae06e42f9468f97d81724101046
diff --git a/properties/tests/conf/tls-inline.ovpn b/properties/tests/conf/tls-inline.ovpn
index 373d6f3..96cd453 100644
Binary files a/properties/tests/conf/tls-inline.ovpn and b/properties/tests/conf/tls-inline.ovpn differ
diff --git a/properties/tests/test-import-export.c b/properties/tests/test-import-export.c
index 5e7d84e..8b629bf 100644
--- a/properties/tests/test-import-export.c
+++ b/properties/tests/test-import-export.c
@@ -332,13 +332,16 @@ test_file_contents (const char *id,
        path2 = g_strdup_printf ("%s/%s-%s.pem", dir, id, item);
        ASSERT(g_file_get_contents(path2, &expected_contents, &expected_length, NULL),
                test, "failed to load test data?!");
-       g_free (path2);
 
-       ASSERT(length == expected_length && !memcmp(contents, expected_contents, length),
-               test, "file contents were not the same");
+       if (length != expected_length || memcmp(contents, expected_contents, length)) {
+               g_message ("a>>>[%s]%s<<<a", path2, expected_contents);
+               g_message ("b>>>[%s]%s<<<b", path, contents);
+               FAIL (test, "file contents were not the same");
+       }
        g_free (contents);
        g_free (expected_contents);
-  g_free (test);
+       g_free (path2);
+       g_free (test);
 }
 
 static void
@@ -1201,15 +1204,15 @@ test_route_import (NMVpnEditorPlugin *plugin,
        const char *expected_dest1 = "1.2.3.0";
        guint32 expected_prefix1   = 24;
        const char *expected_nh1   = "1.2.3.254";
-       guint32 expected_metric1   = 99;
+       gint64 expected_metric1    = 99;
        const char *expected_dest2 = "5.6.7.8";
        guint32 expected_prefix2   = 30;
        const char *expected_nh2   = "0.0.0.0";
-       guint32 expected_metric2   = 0;
+       gint64 expected_metric2    = -1;
        const char *expected_dest3 = "192.168.0.0";
        guint32 expected_prefix3   = 16;
        const char *expected_nh3   = "192.168.44.1";
-       guint32 expected_metric3   = 0;
+       gint64 expected_metric3    = -1;
 
        connection = get_basic_connection (detail, plugin, dir, "route.ovpn");
        ASSERT (connection != NULL, detail, "failed to import connection");
@@ -1232,6 +1235,8 @@ test_route_import (NMVpnEditorPlugin *plugin,
        {
                NMIP4Route *route;
 
+#define METR(metr) ((metr) == -1 ? 0 : ((guint32) (metr)))
+
                num_routes = nm_setting_ip4_config_get_num_routes (s_ip4);
                ASSERT (num_routes == 3, detail, "incorrect number of static routes");
 
@@ -1241,7 +1246,7 @@ test_route_import (NMVpnEditorPlugin *plugin,
                ASSERT (nm_ip4_route_get_prefix (route) == expected_prefix1,
                        detail, "unexpected prefix of 1. route");
                g_assert_cmpint (nm_ip4_route_get_next_hop (route), ==, _addr_from_string (expected_nh1));
-               ASSERT (nm_ip4_route_get_metric (route) == expected_metric1,
+               ASSERT (nm_ip4_route_get_metric (route) == METR (expected_metric1),
                        detail, "unexpected metric of 1. route");
 
                /* route 2 */
@@ -1250,7 +1255,7 @@ test_route_import (NMVpnEditorPlugin *plugin,
                ASSERT (nm_ip4_route_get_prefix (route) == expected_prefix2,
                        detail, "unexpected prefix of 2. route");
                g_assert_cmpint (nm_ip4_route_get_next_hop (route), ==, _addr_from_string (expected_nh2));
-               ASSERT (nm_ip4_route_get_metric (route) == expected_metric2,
+               ASSERT (nm_ip4_route_get_metric (route) == METR (expected_metric2),
                        detail, "unexpected metric of 2. route");
 
                /* route 3 */
@@ -1259,7 +1264,7 @@ test_route_import (NMVpnEditorPlugin *plugin,
                ASSERT (nm_ip4_route_get_prefix (route) == expected_prefix3,
                        detail, "unexpected prefix of 3. route");
                g_assert_cmpint (nm_ip4_route_get_next_hop (route), ==, _addr_from_string (expected_nh3));
-               ASSERT (nm_ip4_route_get_metric (route) == expected_metric3,
+               ASSERT (nm_ip4_route_get_metric (route) == METR (expected_metric3),
                        detail, "unexpected metric of 3. route");
        }
 #else


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]