Commit message: service: disallow newlinies in configuration values (CVE-2018-10900)

The vpnc configuration format doesn't allow those. vpnc(8):

  The values start exactly one space after the keywords, and run to the end
  of line. This lets you put any kind of weird character (except CR, LF and
  NUL) in your strings

We have no choice but to reject them. If we didn't it would allow the
user to inject arbitrary configuration directives with potential
security implications.

https://pulsesecurity.co.nz/advisories/NM-VPNC-Privesc

Reported by: Denis Andzakovic
CVE: None
Vulnerability description: None

Project: NetworkManager-vpnc
File: nm-vpnc-service.c

ensure_killed (gpointer data)
{
	int pid = GPOINTER_TO_INT (data);

	if (kill (pid, 0) == 0)
		kill (pid, SIGKILL);
	waitpid (pid, NULL, 0);

	return FALSE;
}

Project: NetworkManager-vpnc
File: nm-vpnc-service.c

real_new_secrets (NMVpnServicePlugin *plugin,
                  NMConnection *connection,
                  GError **error)
{
	NMVPNCPluginPrivate *priv = NM_VPNC_PLUGIN_GET_PRIVATE (plugin);
	NMSettingVpn *s_vpn;
	const char *secret;

	if (!interactive_available || !priv->interactive) {
		g_set_error_literal (error,
		                     NM_VPN_PLUGIN_ERROR,
		                     NM_VPN_PLUGIN_ERROR_FAILED,
		                     _("Could not use new secrets as interactive mode is disabled."));
		return FALSE;
	}

	s_vpn = nm_connection_get_setting_vpn (connection);
	if (!s_vpn) {
		g_set_error_literal (error,
		                     NM_VPN_PLUGIN_ERROR,
		                     NM_VPN_PLUGIN_ERROR_INVALID_CONNECTION,
		                     _("Could not process the request because the VPN connection settings were invalid."));
		return FALSE;
	}

	if (!priv->pending_auth) {
		g_set_error_literal (error,
		                     NM_VPN_PLUGIN_ERROR,
		                     NM_VPN_PLUGIN_ERROR_INVALID_CONNECTION,
		                     _("Could not process the request because no pending authentication is required."));
		return FALSE;
	}

	_LOGD ("VPN received new secrets; sending to '%s' vpnc stdin", priv->pending_auth);

	secret = nm_setting_vpn_get_secret (s_vpn, priv->pending_auth);
	if (!secret) {
		g_set_error (error,
		             NM_VPN_PLUGIN_ERROR,
		             NM_VPN_PLUGIN_ERROR_INVALID_CONNECTION,
		             _("Could not process the request because the requested info “%s” was not provided."),
		             priv->pending_auth);
		return FALSE;
	}

	/* Ignoring secret flags here; if vpnc requested the item, we must provide it */
	write_config_option (priv->infd, "%s", secret);

	priv->pending_auth = NULL;
	return TRUE;
}

Project: NetworkManager-vpnc
File: nm-vpnc-service.c

setup_signals (void)
{
	struct sigaction action;
	sigset_t mask;

	sigemptyset (&mask);
	action.sa_handler = signal_handler;
	action.sa_mask = mask;
	action.sa_flags = 0;
	sigaction (SIGTERM,  &action, NULL);
	sigaction (SIGINT,  &action, NULL);
}

Project: NetworkManager-vpnc
File: nm-vpnc-service.c

nm_vpnc_config_write (gint vpnc_fd,
                      const char *bus_name,
                      NMSettingConnection *s_con,
                      NMSettingVpn *s_vpn,
                      GError **error)
{
	WriteConfigInfo *info;
	const char *props_username;
	const char *props_natt_mode;
	const char *default_username;
	const char *pw_type;
	const char *local_port;
	const char *interface_name;
	NMSettingSecretFlags secret_flags = NM_SETTING_SECRET_FLAG_NONE;

	if (bus_name) {
		g_assert (g_dbus_is_name (bus_name));
		if (nm_streq (bus_name, NM_DBUS_SERVICE_VPNC))
			bus_name = NULL;
	}

	interface_name = nm_setting_connection_get_interface_name(s_con);

	default_username = nm_setting_vpn_get_user_name (s_vpn);

	write_config_option (vpnc_fd, "Debug %d", gl.log_level_native);

	if (interface_name && strlen(interface_name) > 0)
		write_config_option (vpnc_fd, "Interface name %s", interface_name);

	write_config_option (vpnc_fd, "Script %s %d %ld %s%s",
	                     NM_VPNC_HELPER_PATH,
	                     gl.log_level,
	                     (long) getpid(),
	                     bus_name ? " --bus-name " : "", bus_name ?: "");

	write_config_option (vpnc_fd,
	                     NM_VPNC_KEY_CISCO_UDP_ENCAPS_PORT " %d",
	                     NM_VPNC_UDP_ENCAPSULATION_PORT);

	local_port = nm_setting_vpn_get_data_item (s_vpn, NM_VPNC_KEY_LOCAL_PORT);
	if (!local_port) {
		/* Configure 'Local Port' to 0 (random port) if the value is not set in the setting.
		 * Otherwise vpnc would try to use 500 and could clash with other IKE processes.
		 */
		write_config_option (vpnc_fd,
		                     NM_VPNC_KEY_LOCAL_PORT " %d",
		                     NM_VPNC_LOCAL_PORT_ISAKMP);
	}

	/* Fill username if it's not present */
	props_username = nm_setting_vpn_get_data_item (s_vpn, NM_VPNC_KEY_XAUTH_USER);
	if (   default_username
	    && strlen (default_username)
	    && (!props_username || !strlen (props_username))) {
		write_config_option (vpnc_fd,
		                     NM_VPNC_KEY_XAUTH_USER " %s",
		                     default_username);
	}

	/* Use Cisco UDP by default */
	props_natt_mode = nm_setting_vpn_get_data_item (s_vpn, NM_VPNC_KEY_NAT_TRAVERSAL_MODE);
	if (!props_natt_mode || !strlen (props_natt_mode)) {
		write_config_option (vpnc_fd,
		                     NM_VPNC_KEY_NAT_TRAVERSAL_MODE " %s",
		                     NM_VPNC_NATT_MODE_CISCO);
	} else if (props_natt_mode && (!strcmp (props_natt_mode, NM_VPNC_NATT_MODE_NATT_ALWAYS))) {
		write_config_option (vpnc_fd,
		                     NM_VPNC_KEY_NAT_TRAVERSAL_MODE " %s",
		                     NM_VPNC_NATT_MODE_NATT_ALWAYS);
	}

	info = g_malloc0 (sizeof (WriteConfigInfo));
	info->fd = vpnc_fd;

	/* Check for ignored user password */
	if (nm_setting_get_secret_flags (NM_SETTING (s_vpn), NM_VPNC_KEY_XAUTH_PASSWORD, &secret_flags, NULL)) {
		if (secret_flags & NM_SETTING_SECRET_FLAG_NOT_REQUIRED)
			info->upw_ignored = TRUE;
	} else {
		pw_type = nm_setting_vpn_get_data_item (s_vpn, NM_VPNC_KEY_XAUTH_PASSWORD_TYPE);
		if (pw_type && !strcmp (pw_type, NM_VPNC_PW_TYPE_UNUSED))
			info->upw_ignored = TRUE;
	}

	/* Check for ignored group password */
	secret_flags = NM_SETTING_SECRET_FLAG_NONE;
	if (nm_setting_get_secret_flags (NM_SETTING (s_vpn), NM_VPNC_KEY_SECRET, &secret_flags, NULL)) {
		if (secret_flags & NM_SETTING_SECRET_FLAG_NOT_REQUIRED)
			info->gpw_ignored = TRUE;
	} else {
		pw_type = nm_setting_vpn_get_data_item (s_vpn, NM_VPNC_KEY_SECRET_TYPE);
		if (pw_type && !strcmp (pw_type, NM_VPNC_PW_TYPE_UNUSED))
			info->gpw_ignored = TRUE;
	}

	nm_setting_vpn_foreach_data_item (s_vpn, write_one_property, info);
	nm_setting_vpn_foreach_secret (s_vpn, write_one_property, info);
	*error = info->error;
	g_free (info);

	return *error ? FALSE : TRUE;
}

Project: NetworkManager-vpnc
File: nm-vpnc-service.c

vpnc_watch_cb (GPid pid, gint status, gpointer user_data)
{
	NMVPNCPlugin *plugin = NM_VPNC_PLUGIN (user_data);
	NMVPNCPluginPrivate *priv = NM_VPNC_PLUGIN_GET_PRIVATE (plugin);
	guint error = 0;

	if (WIFEXITED (status)) {
		error = WEXITSTATUS (status);
		if (error != 0)
			_LOGW ("vpnc exited with error code %d", error);
	} else if (WIFSTOPPED (status))
		_LOGW ("vpnc stopped unexpectedly with signal %d", WSTOPSIG (status));
	else if (WIFSIGNALED (status))
		_LOGW ("vpnc died with signal %d", WTERMSIG (status));
	else
		_LOGW ("vpnc died from an unknown cause");

	priv->watch_id = 0;

	/* Grab any remaining output, if any */
	if (priv->out.channel)
		pipe_echo_finish (&priv->out);
	if (priv->err.channel)
		pipe_echo_finish (&priv->err);

	vpnc_cleanup (plugin, FALSE);
	remove_pidfile (plugin);

	/* Must be after data->state is set since signals use data->state */
	switch (error) {
	case 2:
		/* Couldn't log in due to bad user/pass */
		nm_vpn_service_plugin_failure (NM_VPN_SERVICE_PLUGIN (plugin), NM_VPN_PLUGIN_FAILURE_LOGIN_FAILED);
		break;
	case 1:
		/* Other error (couldn't bind to address, etc) */
		nm_vpn_service_plugin_failure (NM_VPN_SERVICE_PLUGIN (plugin), NM_VPN_PLUGIN_FAILURE_CONNECT_FAILED);
		break;
	default:
		nm_vpn_service_plugin_disconnect (NM_VPN_SERVICE_PLUGIN (plugin), NULL);
		break;
	}
}

Project: NetworkManager-vpnc
File: nm-vpnc-service.c

real_disconnect (NMVpnServicePlugin *plugin, GError **error)
{
	vpnc_cleanup (NM_VPNC_PLUGIN (plugin), TRUE);
	return TRUE;
}

Project: NetworkManager-vpnc
File: nm-vpnc-service.c

real_need_secrets (NMVpnServicePlugin *plugin,
                   NMConnection *connection,
                   const char **out_setting_name,
                   GError **error)
{
	NMSettingVpn *s_vpn;
	NMSettingSecretFlags pw_flags;
	const char *pw = NULL;

	g_return_val_if_fail (NM_IS_VPN_SERVICE_PLUGIN (plugin), FALSE);
	g_return_val_if_fail (NM_IS_CONNECTION (connection), FALSE);

	s_vpn = nm_connection_get_setting_vpn (connection);
	if (!s_vpn) {
		g_set_error (error,
		             NM_VPN_PLUGIN_ERROR,
		             NM_VPN_PLUGIN_ERROR_INVALID_CONNECTION,
		             "%s",
		             _("Could not process the request because the VPN connection settings were invalid."));
		return FALSE;
	}

	/* User password */
	pw = nm_setting_vpn_get_secret (s_vpn, NM_VPNC_KEY_SECRET);
	pw_flags = get_pw_flags (s_vpn, NM_VPNC_KEY_SECRET, NM_VPNC_KEY_SECRET_TYPE);
	if (!pw && !(pw_flags & NM_SETTING_SECRET_FLAG_NOT_REQUIRED)) {
		*out_setting_name = NM_SETTING_VPN_SETTING_NAME;
		return TRUE;
	}

	/* Group password */
	pw = nm_setting_vpn_get_secret (s_vpn, NM_VPNC_KEY_XAUTH_PASSWORD);
	pw_flags = get_pw_flags (s_vpn, NM_VPNC_KEY_XAUTH_PASSWORD, NM_VPNC_KEY_XAUTH_PASSWORD_TYPE);
	if (!pw && !(pw_flags & NM_SETTING_SECRET_FLAG_NOT_REQUIRED)) {
		*out_setting_name = NM_SETTING_VPN_SETTING_NAME;
		return TRUE;
	}

	return FALSE;
}

Project: NetworkManager-vpnc
File: nm-vpnc-service.c

real_connect_interactive (NMVpnServicePlugin   *plugin,
                          NMConnection  *connection,
                          GVariant      *details,
                          GError       **error)
{
	if (!interactive_available) {
		g_set_error_literal (error,
		                     NM_VPN_PLUGIN_ERROR,
		                     NM_VPN_PLUGIN_ERROR_INTERACTIVE_NOT_SUPPORTED,
		                     _("vpnc does not support interactive requests"));
		return FALSE;
	}

	if (!_connect_common (plugin, TRUE, connection, details, error))
		return FALSE;

	NM_VPNC_PLUGIN_GET_PRIVATE (plugin)->interactive = TRUE;
	return TRUE;
}

Project: NetworkManager-vpnc
File: nm-vpnc-service.c

find_vpnc (void)
{
	static const char *vpnc_paths[] = {
		"/usr/sbin/vpnc",
		"/sbin/vpnc",
		"/usr/local/sbin/vpnc",
		NULL
	};
	guint i;

	/* Find vpnc */
	for (i = 0; vpnc_paths[i]; i++) {
		if (g_file_test (vpnc_paths[i], G_FILE_TEST_EXISTS))
			return vpnc_paths[i];
	}
	return NULL;
}

Project: NetworkManager-vpnc
File: nm-vpnc-service.c

write_config_option_secret (int fd, const char *key, const char *value)
{
	gs_free char *string = NULL;
	int x;

	string = g_strdup_printf ("%s %s\n", key, value);

	x = write (fd, string, strlen (string));
	if (x < 0)
		_LOGW ("Unexpected error in write(): %d", errno);

	_LOGD ("Config: %s <hidden>", key);
}

Project: NetworkManager-vpnc
File: nm-vpnc-service.c

vpnc_cleanup (NMVPNCPlugin *self, gboolean killit)
{
	NMVPNCPluginPrivate *priv = NM_VPNC_PLUGIN_GET_PRIVATE (self);

	if (priv->infd >= 0) {
		close (priv->infd);
		priv->infd = -1;
	}

	pipe_cleanup (&priv->out);
	pipe_cleanup (&priv->err);
	g_string_truncate (priv->server_message, 0);
	priv->server_message_done = FALSE;

	if (priv->watch_id) {
		g_source_remove (priv->watch_id);
		priv->watch_id = 0;
	}

	if (priv->pid) {
		if (killit) {
			/* Try giving it some time to disconnect cleanly */
			if (kill (priv->pid, SIGTERM) == 0)
				g_timeout_add (2000, ensure_killed, GINT_TO_POINTER (priv->pid));
			_LOGI ("Terminated vpnc daemon with PID %d.", priv->pid);
		} else {
			/* Already quit, just reap the child */
			waitpid (priv->pid, NULL, WNOHANG);
		}
		priv->pid = 0;
	}
}

Project: NetworkManager-vpnc
File: nm-vpnc-service.c

write_one_property (const char *key, const char *value, gpointer user_data)
{
	WriteConfigInfo *info = (WriteConfigInfo *) user_data;
	guint32 type = ITEM_TYPE_UNKNOWN;
	int i;

	if (info->error)
		return;

	/* Find the value in the table to get its type */
	for (i = 0; valid_properties[i].name; i++) {
		ValidProperty prop = valid_properties[i];

		if (!strcmp (prop.name, (char *) key)) {
			/* Property is ok */
			type = prop.type;
			break;
		}
	}

	/* Try the valid secrets table */
	for (i = 0; type == ITEM_TYPE_UNKNOWN && valid_secrets[i].name; i++) {
		ValidProperty prop = valid_secrets[i];

		if (!strcmp (prop.name, (char *) key)) {
			/* Property is ok */
			type = prop.type;
			break;
		}
	}

	if (type == ITEM_TYPE_UNKNOWN) {
		g_set_error (&info->error,
		             NM_VPN_PLUGIN_ERROR,
		             NM_VPN_PLUGIN_ERROR_BAD_ARGUMENTS,
		             _("Config option “%s” invalid or unknown."),
		             (const char *) key);
		return;
	}

	/* Don't write ignored secrets */
	if (!strcmp (key, NM_VPNC_KEY_XAUTH_PASSWORD) && info->upw_ignored)
		return;
	if (!strcmp (key, NM_VPNC_KEY_SECRET) && info->gpw_ignored)
		return;

	if (type == ITEM_TYPE_STRING || type == ITEM_TYPE_PATH)
		write_config_option (info->fd, "%s %s", (char *) key, (char *) value);
	else if (type == ITEM_TYPE_SECRET)
		write_config_option_secret (info->fd, key, value);
	else if (type == ITEM_TYPE_BOOLEAN) {
		if (!strcmp (value, "yes"))
			write_config_option (info->fd, "%s", (char *) key);
	} else if (type == ITEM_TYPE_INT) {
		long int tmp_int;
		char *tmp_str;

		/* Convert -> int and back to string for security's sake since
		 * strtol() ignores leading and trailing characters.
		 */
		errno = 0;
		tmp_int = strtol (value, NULL, 10);
		if (errno == 0) {
			tmp_str = g_strdup_printf ("%ld", tmp_int);
			write_config_option (info->fd, "%s %s", (char *) key, tmp_str);
			g_free (tmp_str);
		} else {
			g_set_error (&info->error,
			             NM_VPN_PLUGIN_ERROR,
			             NM_VPN_PLUGIN_ERROR_BAD_ARGUMENTS,
			             _("Config option “%s” not an integer."),
			             (const char *) key);
		}
	} else if (type == ITEM_TYPE_IGNORED) {
		/* ignored */
	} else {
		/* Just ignore unknown properties */
		_LOGW ("Don't know how to write property '%s' with type %d", key, type);
	}
}

Project: NetworkManager-vpnc
File: nm-vpnc-service.c

vpnc_prompt (const char *data, gsize dlen, gpointer user_data)
{
	NMVPNCPlugin *plugin = NM_VPNC_PLUGIN (user_data);
	NMVPNCPluginPrivate *priv = NM_VPNC_PLUGIN_GET_PRIVATE (plugin);
	const char *hints[2] = { NULL, NULL };
	char *prompt;
	guint i;

	g_warn_if_fail (priv->pending_auth == NULL);
	priv->pending_auth = NULL;

	prompt = g_strndup (data, dlen);
	_LOGD ("vpnc requested input: '%s'", prompt);
	for (i = 0; i < G_N_ELEMENTS (phmap); i++) {
		if (g_str_has_prefix (prompt, phmap[i].prompt)) {
			hints[0] = phmap[i].hint;
			break;
		}
	}

	if (!hints[0]) {
		_LOGD ("Unhandled vpnc message '%s'", prompt);
		g_free (prompt);
		return;
	}

	_LOGD ("Requesting new secrets: '%s' (%s)", prompt, hints[0]);

	nm_vpn_service_plugin_secrets_required (NM_VPN_SERVICE_PLUGIN (plugin),
	                                priv->server_message->len ? priv->server_message->str : prompt,
	                                (const char **) hints);
	g_string_truncate (priv->server_message, 0);
	g_free (prompt);

	priv->pending_auth = hints[0];
}

Project: NetworkManager-vpnc
File: nm-vpnc-service.c

pipe_echo_finish (Pipe *pipe)
{
	GIOStatus status;
	gsize bytes_read;
	char buf[512];

	do {
		bytes_read = 0;
		status = g_io_channel_read_chars (pipe->channel,
		                                  buf,
		                                  sizeof (buf),
		                                  &bytes_read,
		                                  NULL);
		if (bytes_read) {
			fprintf (pipe->logf, "%.*s", (int) bytes_read, buf);
			fflush (pipe->logf);
		}
	} while (status == G_IO_STATUS_NORMAL);
}

Project: NetworkManager-vpnc
File: nm-vpnc-service.c

remove_pidfile (NMVPNCPlugin *plugin)
{
	NMVPNCPluginPrivate *priv = NM_VPNC_PLUGIN_GET_PRIVATE (plugin);

	if (priv->pid_file) {
		unlink (priv->pid_file);
		g_free (priv->pid_file);
		priv->pid_file = NULL;
	}
}

Project: NetworkManager-vpnc
File: nm-vpnc-service.c

quit_mainloop (NMVPNCPlugin *plugin, gpointer user_data)
{
	g_main_loop_quit ((GMainLoop *) user_data);
}

Project: NetworkManager-vpnc
File: nm-vpnc-service.c

signal_handler (int signo)
{
	if (signo == SIGINT || signo == SIGTERM)
		g_main_loop_quit (gl.loop);
}

Project: NetworkManager-vpnc
File: nm-vpnc-service.c

nm_vpnc_secrets_validate (NMSettingVpn *s_vpn,
                          gboolean allow_missing,
                          GError **error)
{
	GError *validate_error = NULL;
	ValidateInfo info = { &valid_secrets[0], &validate_error, FALSE };

	nm_setting_vpn_foreach_secret (s_vpn, validate_one_property, &info);
	if (validate_error) {
		g_propagate_error (error, validate_error);
		return FALSE;
	}

	if (allow_missing == FALSE && !info.have_items) {
		g_set_error (error,
		             NM_VPN_PLUGIN_ERROR,
		             NM_VPN_PLUGIN_ERROR_BAD_ARGUMENTS,
		             "%s",
		             _("No VPN secrets!"));
		return FALSE;
	}

	return TRUE;
}

Project: NetworkManager-vpnc
File: nm-vpnc-service.c

pipe_cleanup (Pipe *pipe)
{
	if (pipe->channel) {
		g_source_remove (pipe->watch);
		pipe->watch = 0;
		g_io_channel_shutdown (pipe->channel, FALSE, NULL);
		g_io_channel_unref (pipe->channel);
		pipe->channel = NULL;
	}
	if (pipe->fd >= 0) {
		close (pipe->fd);
		pipe->fd = -1;
	}
	if (pipe->buf) {
		g_string_free (pipe->buf, TRUE);
		pipe->buf = NULL;
	}
}

Project: NetworkManager-vpnc
File: nm-vpnc-service.c

nm_vpnc_plugin_class_init (NMVPNCPluginClass *vpnc_class)
{
	GObjectClass *object_class = G_OBJECT_CLASS (vpnc_class);
	NMVpnServicePluginClass *parent_class = NM_VPN_SERVICE_PLUGIN_CLASS (vpnc_class);

	g_type_class_add_private (object_class, sizeof (NMVPNCPluginPrivate));

	/* virtual methods */
	parent_class->connect = real_connect;
	parent_class->connect_interactive = real_connect_interactive;
	parent_class->need_secrets = real_need_secrets;
	parent_class->disconnect = real_disconnect;
	parent_class->new_secrets = real_new_secrets;
}

Project: NetworkManager-vpnc
File: nm-vpnc-service.c

data_available (GIOChannel *source,
                GIOCondition condition,
                gpointer data)
{
	NMVPNCPlugin *plugin = NM_VPNC_PLUGIN (data);
	NMVPNCPluginPrivate *priv = NM_VPNC_PLUGIN_GET_PRIVATE (plugin);
	GError *error = NULL;
	Pipe *pipe = NULL;
	gsize bytes_read = 0;
	GIOStatus status;

	/* Figure out which pipe we're using */
	if (source == priv->out.channel)
		pipe = &priv->out;
	else if (source == priv->err.channel)
		pipe = &priv->err;
	else
		g_assert_not_reached ();

	if (condition & G_IO_ERR) {
		_LOGW ("Unexpected vpnc pipe error");
		goto fail;
	}

	do {
		gsize consumed = 0;
		char buf[512];

		status = g_io_channel_read_chars (source,
		                                  buf,
		                                  sizeof (buf),
		                                  &bytes_read,
		                                  &error);
		if (status == G_IO_STATUS_ERROR) {
			if (error)
				_LOGW ("vpnc read error: %s", error->message);
			g_clear_error (&error);
		}

		if (bytes_read) {
			g_string_append_len (pipe->buf, buf, bytes_read);

			do {
				consumed = utils_handle_output (pipe->buf,
				                                priv->server_message,
				                                &priv->server_message_done,
				                                vpnc_prompt,
				                                plugin);
				if (consumed) {
					/* Log all output to the console */
					fprintf (pipe->logf, "%.*s", (int) consumed, pipe->buf->str);
					fflush (pipe->logf);

					/* If output was handled, clear the buffer */
					g_string_erase (pipe->buf, 0, consumed);
				}
			} while (consumed);
		}

		if (status == G_IO_STATUS_EOF)
			goto fail;
	} while (bytes_read);

	return TRUE;

fail:
	pipe->watch = 0;
	return FALSE;
}

Project: NetworkManager-vpnc
File: nm-vpnc-service.c

nm_vpnc_properties_validate (NMSettingVpn *s_vpn, GError **error)
{
	ValidateInfo info = { &valid_properties[0], error, FALSE };

	nm_setting_vpn_foreach_data_item (s_vpn, validate_one_property, &info);
	if (!info.have_items) {
		g_set_error (error,
		             NM_VPN_PLUGIN_ERROR,
		             NM_VPN_PLUGIN_ERROR_BAD_ARGUMENTS,
		             "%s",
		             _("No VPN configuration options."));
		return FALSE;
	}

	return *error ? FALSE : TRUE;
}

Project: NetworkManager-vpnc
File: nm-vpnc-service.c

real_connect (NMVpnServicePlugin   *plugin,
              NMConnection  *connection,
              GError       **error)
{
	return _connect_common (plugin, FALSE, connection, NULL, error);
}

Project: NetworkManager-vpnc
File: nm-vpnc-service.c

vpnc_check_interactive (void)
{
	const char *vpnc_path;
	const char *argv[3];
	GError *error = NULL;
	char *output = NULL;
	gboolean has_interactive = FALSE;

	vpnc_path = find_vpnc ();
	if (!vpnc_path) {
		_LOGW ("Failed to find vpnc for version check");
		return FALSE;
	}

	argv[0] = vpnc_path;
	argv[1] = "--long-help";
	argv[2] = NULL;
	if (g_spawn_sync ("/", (char **) argv, NULL, G_SPAWN_STDERR_TO_DEV_NULL, NULL, NULL, &output, NULL, NULL, &error)) {
		if (strstr (output, "--password-helper"))
			has_interactive = TRUE;
		g_free (output);
	} else {
		_LOGW ("Failed to start vpnc for version check: %s", error->message);
		g_error_free (error);
	}

	return has_interactive;
}

Project: NetworkManager-vpnc
File: nm-vpnc-service.c

_connect_common (NMVpnServicePlugin   *plugin,
                 gboolean       interactive,
                 NMConnection  *connection,
                 GVariant      *details,
                 GError       **error)
{
	NMVPNCPluginPrivate *priv = NM_VPNC_PLUGIN_GET_PRIVATE (plugin);
	NMSettingVpn *s_vpn;
	NMSettingConnection *s_con;
	char end[] = { 0x04 };
	gs_free char *bus_name = NULL;

	s_con = nm_connection_get_setting_connection (connection);
	g_assert (s_con);

	s_vpn = nm_connection_get_setting_vpn (connection);
	g_assert (s_vpn);

	if (!nm_vpnc_properties_validate (s_vpn, error))
		goto out;

	if (!nm_vpnc_secrets_validate (s_vpn, interactive, error))
		goto out;

	priv->pid_file = g_strdup_printf (NM_VPNC_PID_PATH "/nm-vpnc-%s.pid", nm_connection_get_uuid (connection));

	if (!nm_vpnc_start_vpnc_binary (NM_VPNC_PLUGIN (plugin), interactive, error))
		goto out;

	if (_LOGD_enabled () || getenv ("NM_VPNC_DUMP_CONNECTION")) {
		_LOGD ("connection:");
		nm_connection_dump (connection);
	}

	g_object_get (plugin, NM_VPN_SERVICE_PLUGIN_DBUS_SERVICE_NAME, &bus_name, NULL);
	if (!nm_vpnc_config_write (priv->infd, bus_name, s_con, s_vpn, error))
		goto out;

	if (interactive) {
		if (write (priv->infd, &end, sizeof (end)) < 0)
			_LOGW ("Unexpected error in write(): %d", errno);
	} else {
		close (priv->infd);
		priv->infd = -1;
	}

	return TRUE;

out:
	vpnc_cleanup (NM_VPNC_PLUGIN (plugin), TRUE);
	return FALSE;
}

Project: NetworkManager-vpnc
File: nm-vpnc-service.c

nm_vpnc_plugin_init (NMVPNCPlugin *plugin)
{
	NMVPNCPluginPrivate *priv = NM_VPNC_PLUGIN_GET_PRIVATE (plugin);

	priv->infd = -1;
	priv->out.fd = -1;
	priv->err.fd = -1;
	priv->server_message = g_string_sized_new (30);
}

Project: NetworkManager-vpnc
File: nm-vpnc-service.c

validate_one_property (const char *key, const char *value, gpointer user_data)
{
	ValidateInfo *info = (ValidateInfo *) user_data;
	ValidProperty *prop = NULL;
	long int tmp;
	int i;

	if (*(info->error))
		return;

	info->have_items = TRUE;

	/* 'name' is the setting name; always allowed but unused */
	if (!strcmp (key, NM_SETTING_NAME))
		return;

	for (i = 0; info->table[i].name; i++) {
		prop = &info->table[i];
		if (g_strcmp0 (prop->name, key) == 0)
			break;
	}

	/* Did not find the property from valid_properties or the type did not match */
	if (!prop || !prop->name) {
		g_set_error (info->error,
		             NM_VPN_PLUGIN_ERROR,
		             NM_VPN_PLUGIN_ERROR_BAD_ARGUMENTS,
		             _("property “%s” invalid or not supported"),
		             key);
		return;
	}

	/* Validate the property */
	switch (prop->type) {
	case ITEM_TYPE_IGNORED:
		break; /* technically valid, but unused */
	case ITEM_TYPE_STRING:
	case ITEM_TYPE_SECRET:
		if (strchr (value, '\n') || strchr (value, '\r')) {
			g_set_error (info->error,
			             NM_VPN_PLUGIN_ERROR,
			             NM_VPN_PLUGIN_ERROR_BAD_ARGUMENTS,
			             _("property “%s” contains a newline character"),
			             key);
		}
		break;
	case ITEM_TYPE_PATH:
		if (   !value
		    || !strlen (value)
		    || !g_path_is_absolute (value)
		    || !g_file_test (value, G_FILE_TEST_EXISTS)) {
			g_set_error (info->error,
			             NM_VPN_PLUGIN_ERROR,
			             NM_VPN_PLUGIN_ERROR_BAD_ARGUMENTS,
			             _("property “%s” file path “%s” is not absolute or does not exist"),
			             key, value);
		}
		break;
	case ITEM_TYPE_INT:
		errno = 0;
		tmp = strtol (value, NULL, 10);
		if (errno == 0 && tmp >= prop->int_min && tmp <= prop->int_max)
			break; /* valid */

		g_set_error (info->error,
		             NM_VPN_PLUGIN_ERROR,
		             NM_VPN_PLUGIN_ERROR_BAD_ARGUMENTS,
		             _("invalid integer property “%s” or out of range [%d -> %d]"),
		             key, prop->int_min, prop->int_max);
		break;
	case ITEM_TYPE_BOOLEAN:
		if (!strcmp (value, "yes") || !strcmp (value, "no"))
			break; /* valid */

		g_set_error (info->error,
		             NM_VPN_PLUGIN_ERROR,
		             NM_VPN_PLUGIN_ERROR_BAD_ARGUMENTS,
		             _("invalid boolean property “%s” (not yes or no)"),
		             key);
		break;
	default:
		g_set_error (info->error,
		             NM_VPN_PLUGIN_ERROR,
		             NM_VPN_PLUGIN_ERROR_BAD_ARGUMENTS,
		             _("unhandled property “%s” type %d"),
		             key, prop->type);
		break;
	}
}

Project: NetworkManager-vpnc
File: nm-vpnc-service.c

nm_vpnc_plugin_new (const char *bus_name)
{
	NMVPNCPlugin *plugin;
	GError *error = NULL;

	plugin = (NMVPNCPlugin *) g_initable_new (NM_TYPE_VPNC_PLUGIN, NULL, &error,
	                                          NM_VPN_SERVICE_PLUGIN_DBUS_SERVICE_NAME, bus_name,
	                                          NM_VPN_SERVICE_PLUGIN_DBUS_WATCH_PEER, !gl.debug,
	                                          NULL);
	if (!plugin) {
		_LOGW ("Failed to initialize a plugin instance: %s", error->message);
		g_error_free (error);
	}

	return plugin;
}

Project: NetworkManager-vpnc
File: nm-vpnc-service.c

main (int argc, char *argv[])
{
	NMVPNCPlugin *plugin;
	gboolean persist = FALSE;
	GOptionContext *opt_ctx = NULL;
	gs_free char *bus_name_free = NULL;
	const char *bus_name;
	GError *error = NULL;

	GOptionEntry options[] = {
		{ "persist", 0, 0, G_OPTION_ARG_NONE, &persist, N_("Don’t quit when VPN connection terminates"), NULL },
		{ "debug", 0, 0, G_OPTION_ARG_NONE, &gl.debug, N_("Enable verbose debug logging (may expose passwords)"), NULL },
		{ "bus-name", 0, 0, G_OPTION_ARG_STRING, &bus_name_free, N_("D-Bus name to use for this instance"), NULL },
		{NULL}
	};

#if !GLIB_CHECK_VERSION (2, 35, 0)
	g_type_init ();
#endif

	/* locale will be set according to environment LC_* variables */
	setlocale (LC_ALL, "");

	bindtextdomain (GETTEXT_PACKAGE, NM_VPNC_LOCALEDIR);
	bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
	textdomain (GETTEXT_PACKAGE);

	/* Parse options */
	opt_ctx = g_option_context_new (NULL);
	g_option_context_set_translation_domain (opt_ctx, GETTEXT_PACKAGE);
	g_option_context_set_ignore_unknown_options (opt_ctx, FALSE);
	g_option_context_set_help_enabled (opt_ctx, TRUE);
	g_option_context_add_main_entries (opt_ctx, options, NULL);

	g_option_context_set_summary (opt_ctx,
	                              _("nm-vpnc-service provides integrated "
	                                "Cisco Legacy IPsec VPN capability to NetworkManager."));

	if (!g_option_context_parse (opt_ctx, &argc, &argv, &error)) {
		g_printerr ("Error parsing the command line options: %s\n", error->message);
		g_option_context_free (opt_ctx);
		g_clear_error (&error);
		exit (EXIT_FAILURE);
	}

	g_option_context_free (opt_ctx);

	bus_name = bus_name_free ?: NM_DBUS_SERVICE_VPNC;
	if (!g_dbus_is_name (bus_name)) {
		g_printerr ("invalid --bus-name\n");
		exit (EXIT_FAILURE);
	}

	interactive_available = vpnc_check_interactive ();

	if (getenv ("VPNC_DEBUG"))
		gl.debug = TRUE;

	gl.log_level = _nm_utils_ascii_str_to_int64 (getenv ("NM_VPN_LOG_LEVEL"),
	                                             10, 0, LOG_DEBUG, -1);

	if (gl.log_level < 0)
		gl.log_level_native = gl.debug ? 3 : 0;
	else if (gl.log_level <= 0)
		gl.log_level_native = 0;
	else if (gl.log_level <= LOG_WARNING)
		gl.log_level_native = 1;
	else if (gl.log_level <= LOG_NOTICE)
		gl.log_level_native = 2;
	else if (gl.log_level <= LOG_INFO)
		gl.log_level_native = 3;
	else {
		/* level 99 prints passwords. We don't want that even for the highest
		 * level. So, choose one below. */
		gl.log_level_native = 98;
	}

	if (gl.log_level < 0)
		gl.log_level = gl.debug ? LOG_DEBUG : LOG_NOTICE;

	_LOGD ("nm-vpnc-service (version " DIST_VERSION ") starting...");
	_LOGD ("   vpnc interactive mode is %s", interactive_available ? "enabled" : "disabled");
	_LOGD ("   uses%s --bus-name \"%s\"", bus_name_free ? "" : " default", bus_name);

	if (system ("/sbin/modprobe tun") == -1)
		exit (EXIT_FAILURE);

	plugin = nm_vpnc_plugin_new (bus_name);
	if (!plugin)
		exit (EXIT_FAILURE);

	_LOGD ("nm-vpnc-service (version " DIST_VERSION ") started.");

	gl.loop = g_main_loop_new (NULL, FALSE);

	if (!persist)
		g_signal_connect (plugin, "quit", G_CALLBACK (quit_mainloop), gl.loop);

	setup_signals ();
	g_main_loop_run (gl.loop);

	remove_pidfile (plugin);

	g_main_loop_unref (gl.loop);
	gl.loop = NULL;
	g_object_unref (plugin);

	exit (EXIT_SUCCESS);
}

Project: NetworkManager-vpnc
File: nm-vpnc-service.c

nm_vpnc_start_vpnc_binary (NMVPNCPlugin *plugin, gboolean interactive, GError **error)
{
	NMVPNCPluginPrivate *priv = NM_VPNC_PLUGIN_GET_PRIVATE (plugin);
	const char *vpnc_path;
	const char *args[10];
	guint i = 0;

	g_return_val_if_fail (priv->pid == 0, FALSE);
	g_return_val_if_fail (priv->infd == -1, FALSE);
	g_return_val_if_fail (priv->out.fd == -1, FALSE);
	g_return_val_if_fail (priv->err.fd == -1, FALSE);

	vpnc_path = find_vpnc ();
	if (!vpnc_path) {
		g_set_error_literal (error,
		                     NM_VPN_PLUGIN_ERROR,
		                     NM_VPN_PLUGIN_ERROR_LAUNCH_FAILED,
		                     _("Could not find vpnc binary."));
		return FALSE;
	}

	args[i++] = vpnc_path;
	args[i++] = "--no-detach";
	args[i++] = "--pid-file";
	args[i++] = priv->pid_file;
	if (!interactive)
		args[i++] = "--non-inter";
	args[i++] = "-";
	args[i++] = NULL;
	if (!g_spawn_async_with_pipes (NULL,
	                               (char **) args,
	                               NULL,
	                               G_SPAWN_DO_NOT_REAP_CHILD,
	                               NULL,
	                               NULL,
	                               &priv->pid,
	                               &priv->infd,
	                               interactive ? &priv->out.fd : NULL,
	                               interactive ? &priv->err.fd : NULL,
	                               error)) {
		_LOGW ("vpnc failed to start.  error: '%s'", (*error)->message);
		return FALSE;
	}
	_LOGI ("vpnc started with pid %d", priv->pid);

	priv->watch_id = g_child_watch_add (priv->pid, vpnc_watch_cb, plugin);

	if (interactive) {
		/* Watch stdout and stderr */
		pipe_setup (&priv->out, stdout, plugin);
		pipe_setup (&priv->err, stderr, plugin);
	}
	return TRUE;
}

Project: NetworkManager-vpnc
File: nm-vpnc-service.c

write_config_option (int fd, const char *format, ...)
{
	gs_free char *string = NULL;
	va_list args;
	int x;

	va_start (args, format);
	string = g_strdup_vprintf (format, args);
	va_end (args);

	x = write (fd, string, strlen (string));
	if (x < 0)
		_LOGW ("Unexpected error in write(): %d", errno);
	x = write (fd, "\n", 1);
	if (x < 0)
		_LOGW ("Unexpected error in write(): %d", errno);

	_LOGD ("Config: %s", string);
}

Project: NetworkManager-vpnc
File: nm-vpnc-service.c

_LOGD_enabled (void)
{
	return gl.log_level >= LOG_INFO;
}

Project: NetworkManager-vpnc
File: nm-vpnc-service.c

pipe_setup (Pipe *pipe, FILE *logf, gpointer user_data)
{
	GIOFlags flags = 0;

	pipe->logf = logf;
	pipe->buf = g_string_sized_new (512);

	pipe->channel = g_io_channel_unix_new (pipe->fd);
	g_io_channel_set_encoding (pipe->channel, NULL, NULL);
	flags = g_io_channel_get_flags (pipe->channel);
	g_io_channel_set_flags (pipe->channel, flags | G_IO_FLAG_NONBLOCK, NULL);
	g_io_channel_set_buffered (pipe->channel, FALSE);

	pipe->watch = g_io_add_watch (pipe->channel,
	                              G_IO_IN | G_IO_ERR | G_IO_PRI,
	                              data_available,
	                              user_data);
}

Project: NetworkManager-vpnc
File: nm-vpnc-service.c

get_pw_flags (NMSettingVpn *s_vpn, const char *secret_name, const char *type_name)
{
	const char *val;
	NMSettingSecretFlags flags = NM_SETTING_SECRET_FLAG_NONE;

	/* Try new flags value first */
	if (nm_setting_get_secret_flags (NM_SETTING (s_vpn), secret_name, &flags, NULL))
		return flags;

	/* Otherwise try old "password type" value */
	val = nm_setting_vpn_get_data_item (s_vpn, type_name);
	if (val) {
		if (g_strcmp0 (val, NM_VPNC_PW_TYPE_ASK) == 0)
			return NM_SETTING_SECRET_FLAG_NOT_SAVED;
		else if (g_strcmp0 (val, NM_VPNC_PW_TYPE_UNUSED) == 0)
			return NM_SETTING_SECRET_FLAG_NOT_REQUIRED;

		/* NM_VPNC_PW_TYPE_SAVE means FLAG_NONE */
	}

	return NM_SETTING_SECRET_FLAG_NONE;
}