File:  [ELWIX - Embedded LightWeight unIX -] / embedaddon / strongswan / src / charon-nm / nm / nm_service.c
Revision 1.1.1.2 (vendor branch): download - view: text, annotated - select for diffs - revision graph
Wed Mar 17 00:20:08 2021 UTC (3 years, 5 months ago) by misho
Branches: strongswan, MAIN
CVS tags: v5_9_2p0, HEAD
strongswan 5.9.2

    1: /*
    2:  * Copyright (C) 2017 Lubomir Rintel
    3:  *
    4:  * Copyright (C) 2013-2020 Tobias Brunner
    5:  * Copyright (C) 2008-2009 Martin Willi
    6:  * HSR Hochschule fuer Technik Rapperswil
    7:  *
    8:  * This program is free software; you can redistribute it and/or modify it
    9:  * under the terms of the GNU General Public License as published by the
   10:  * Free Software Foundation; either version 2 of the License, or (at your
   11:  * option) any later version.  See <http://www.fsf.org/copyleft/gpl.txt>.
   12:  *
   13:  * This program is distributed in the hope that it will be useful, but
   14:  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
   15:  * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
   16:  * for more details.
   17:  */
   18: 
   19: #include "nm_service.h"
   20: 
   21: #include <daemon.h>
   22: #include <networking/host.h>
   23: #include <utils/identification.h>
   24: #include <config/peer_cfg.h>
   25: #include <credentials/certificates/x509.h>
   26: #include <networking/tun_device.h>
   27: 
   28: #include <stdio.h>
   29: 
   30: /**
   31:  * Private data of NMStrongswanPlugin
   32:  */
   33: typedef struct {
   34: 	/* implements bus listener interface */
   35: 	listener_t listener;
   36: 	/* IKE_SA we are listening on */
   37: 	ike_sa_t *ike_sa;
   38: 	/* backref to public plugin */
   39: 	NMVpnServicePlugin *plugin;
   40: 	/* credentials to use for authentication */
   41: 	nm_creds_t *creds;
   42: 	/* attribute handler for DNS/NBNS server information */
   43: 	nm_handler_t *handler;
   44: 	/* dummy TUN device */
   45: 	tun_device_t *tun;
   46: 	/* name of the connection */
   47: 	char *name;
   48: } NMStrongswanPluginPrivate;
   49: 
   50: G_DEFINE_TYPE_WITH_PRIVATE(NMStrongswanPlugin, nm_strongswan_plugin, NM_TYPE_VPN_SERVICE_PLUGIN)
   51: 
   52: #define NM_STRONGSWAN_PLUGIN_GET_PRIVATE(o) \
   53: 			((NMStrongswanPluginPrivate*) \
   54: 				nm_strongswan_plugin_get_instance_private (o))
   55: 
   56: /**
   57:  * Convert an address chunk to a GValue
   58:  */
   59: static GVariant *addr_to_variant(chunk_t addr)
   60: {
   61: 	GVariantBuilder builder;
   62: 	int i;
   63: 
   64: 	switch (addr.len)
   65: 	{
   66: 		case 4:
   67: 			return g_variant_new_uint32 (*(uint32_t*)addr.ptr);
   68: 		case 16:
   69: 			g_variant_builder_init (&builder, G_VARIANT_TYPE ("ay"));
   70: 			for (i = 0; i < addr.len; i++)
   71: 			{
   72: 				g_variant_builder_add (&builder, "y", addr.ptr[i]);
   73: 
   74: 			}
   75: 			return g_variant_builder_end (&builder);
   76: 		default:
   77: 			return NULL;
   78: 	}
   79: }
   80: 
   81: /**
   82:  * Convert a host to a GValue
   83:  */
   84: static GVariant *host_to_variant(host_t *host)
   85: {
   86: 	return addr_to_variant(host->get_address(host));
   87: }
   88: 
   89: /**
   90:  * Convert enumerated handler chunks to a GValue
   91:  */
   92: static GVariant* handler_to_variant(nm_handler_t *handler, char *variant_type,
   93: 							 configuration_attribute_type_t type)
   94: {
   95: 	GVariantBuilder builder;
   96: 	enumerator_t *enumerator;
   97: 	chunk_t *chunk;
   98: 
   99: 	g_variant_builder_init (&builder, G_VARIANT_TYPE (variant_type));
  100: 
  101: 	enumerator = handler->create_enumerator(handler, type);
  102: 	while (enumerator->enumerate(enumerator, &chunk))
  103: 	{
  104: 		g_variant_builder_add_value (&builder, addr_to_variant(*chunk));
  105: 	}
  106: 	enumerator->destroy(enumerator);
  107: 
  108: 	return g_variant_builder_end (&builder);
  109: }
  110: 
  111: /**
  112:  * Signal IP config to NM, set connection as established
  113:  */
  114: static void signal_ip_config(NMVpnServicePlugin *plugin,
  115: 							 ike_sa_t *ike_sa, child_sa_t *child_sa)
  116: {
  117: 	NMStrongswanPlugin *pub = (NMStrongswanPlugin*)plugin;
  118: 	NMStrongswanPluginPrivate *priv = NM_STRONGSWAN_PLUGIN_GET_PRIVATE(pub);
  119: 	GVariantBuilder builder, ip4builder, ip6builder;
  120: 	GVariant *ip4config, *ip6config;
  121: 	enumerator_t *enumerator;
  122: 	host_t *me, *other, *vip4 = NULL, *vip6 = NULL;
  123: 	nm_handler_t *handler;
  124: 
  125: 	g_variant_builder_init (&builder, G_VARIANT_TYPE_VARDICT);
  126: 	g_variant_builder_init (&ip4builder, G_VARIANT_TYPE_VARDICT);
  127: 	g_variant_builder_init (&ip6builder, G_VARIANT_TYPE_VARDICT);
  128: 
  129: 	handler = priv->handler;
  130: 
  131: 	/* NM apparently requires to know the gateway */
  132: 	other = ike_sa->get_other_host(ike_sa);
  133: 	g_variant_builder_add (&builder, "{sv}", NM_VPN_PLUGIN_CONFIG_EXT_GATEWAY,
  134: 						   host_to_variant(other));
  135: 
  136: 	/* systemd-resolved requires a device to properly install DNS servers, but
  137: 	 * Netkey does not use one.  Passing the physical interface is not ideal,
  138: 	 * as NM fiddles around with it and systemd-resolved likes a separate
  139: 	 * device. So we pass a dummy TUN device along for NM etc. to play with...
  140: 	 */
  141: 	if (priv->tun)
  142: 	{
  143: 		g_variant_builder_add (&builder, "{sv}", NM_VPN_PLUGIN_CONFIG_TUNDEV,
  144: 							   g_variant_new_string (priv->tun->get_name(priv->tun)));
  145: 	}
  146: 
  147: 	/* pass the first virtual IPs we got or use the physical IP */
  148: 	enumerator = ike_sa->create_virtual_ip_enumerator(ike_sa, TRUE);
  149: 	while (enumerator->enumerate(enumerator, &me))
  150: 	{
  151: 		switch (me->get_family(me))
  152: 		{
  153: 			case AF_INET:
  154: 				if (!vip4)
  155: 				{
  156: 					vip4 = me;
  157: 				}
  158: 				break;
  159: 			case AF_INET6:
  160: 				if (!vip6)
  161: 				{
  162: 					vip6 = me;
  163: 				}
  164: 				break;
  165: 		}
  166: 	}
  167: 	enumerator->destroy(enumerator);
  168: 	if (!vip4 && !vip6)
  169: 	{
  170: 		me = ike_sa->get_my_host(ike_sa);
  171: 		switch (me->get_family(me))
  172: 		{
  173: 			case AF_INET:
  174: 				vip4 = me;
  175: 				break;
  176: 			case AF_INET6:
  177: 				vip6 = me;
  178: 				break;
  179: 		}
  180: 	}
  181: 
  182: 	if (vip4)
  183: 	{
  184: 		g_variant_builder_add (&ip4builder, "{sv}", NM_VPN_PLUGIN_IP4_CONFIG_ADDRESS,
  185: 							   host_to_variant(vip4));
  186: 		g_variant_builder_add (&ip4builder, "{sv}", NM_VPN_PLUGIN_IP4_CONFIG_PREFIX,
  187: 							   g_variant_new_uint32 (vip4->get_address(vip4).len * 8));
  188: 
  189: 		/* prevent NM from changing the default route. we set our own route in our
  190: 		 * own routing table
  191: 		 */
  192: 		g_variant_builder_add (&ip4builder, "{sv}", NM_VPN_PLUGIN_IP4_CONFIG_NEVER_DEFAULT,
  193: 							   g_variant_new_boolean (TRUE));
  194: 
  195: 		g_variant_builder_add (&ip4builder, "{sv}", NM_VPN_PLUGIN_IP4_CONFIG_DNS,
  196: 							   handler_to_variant(handler, "au", INTERNAL_IP4_DNS));
  197: 
  198: 		g_variant_builder_add (&ip4builder, "{sv}", NM_VPN_PLUGIN_IP4_CONFIG_NBNS,
  199: 							   handler_to_variant(handler, "au", INTERNAL_IP4_NBNS));
  200: 	}
  201: 
  202: 	if (vip6)
  203: 	{
  204: 		g_variant_builder_add (&ip6builder, "{sv}", NM_VPN_PLUGIN_IP6_CONFIG_ADDRESS,
  205: 							   host_to_variant(vip6));
  206: 		g_variant_builder_add (&ip6builder, "{sv}", NM_VPN_PLUGIN_IP6_CONFIG_PREFIX,
  207: 							   g_variant_new_uint32 (vip6->get_address(vip6).len * 8));
  208: 		g_variant_builder_add (&ip6builder, "{sv}", NM_VPN_PLUGIN_IP6_CONFIG_NEVER_DEFAULT,
  209: 							   g_variant_new_boolean (TRUE));
  210: 		g_variant_builder_add (&ip6builder, "{sv}", NM_VPN_PLUGIN_IP6_CONFIG_DNS,
  211: 							   handler_to_variant(handler, "aay", INTERNAL_IP6_DNS));
  212: 		/* NM_VPN_PLUGIN_IP6_CONFIG_NBNS is not defined */
  213: 	}
  214: 
  215: 	ip4config = g_variant_builder_end (&ip4builder);
  216: 	if (g_variant_n_children (ip4config))
  217: 	{
  218: 		g_variant_builder_add (&builder, "{sv}", NM_VPN_PLUGIN_CONFIG_HAS_IP4,
  219: 							   g_variant_new_boolean (TRUE));
  220: 	}
  221: 	else
  222: 	{
  223: 		g_variant_unref (ip4config);
  224: 		ip4config = NULL;
  225: 	}
  226: 
  227: 	ip6config = g_variant_builder_end (&ip6builder);
  228: 	if (g_variant_n_children (ip6config))
  229: 	{
  230: 		g_variant_builder_add (&builder, "{sv}", NM_VPN_PLUGIN_CONFIG_HAS_IP6,
  231: 							   g_variant_new_boolean (TRUE));
  232: 	}
  233: 	else
  234: 	{
  235: 		g_variant_unref (ip6config);
  236: 		ip6config = NULL;
  237: 	}
  238: 
  239: 	handler->reset(handler);
  240: 
  241: 	nm_vpn_service_plugin_set_config (plugin, g_variant_builder_end (&builder));
  242: 	if (ip4config)
  243: 	{
  244: 		nm_vpn_service_plugin_set_ip4_config (plugin, ip4config);
  245: 	}
  246: 	if (ip6config)
  247: 	{
  248: 		nm_vpn_service_plugin_set_ip6_config (plugin, ip6config);
  249: 	}
  250: }
  251: 
  252: /**
  253:  * signal failure to NM, connecting failed
  254:  */
  255: static void signal_failure(NMVpnServicePlugin *plugin, NMVpnPluginFailure failure)
  256: {
  257: 	NMStrongswanPlugin *pub = (NMStrongswanPlugin*)plugin;
  258: 	nm_handler_t *handler = NM_STRONGSWAN_PLUGIN_GET_PRIVATE(pub)->handler;
  259: 
  260: 	handler->reset(handler);
  261: 
  262: 	nm_vpn_service_plugin_failure(plugin, failure);
  263: }
  264: 
  265: METHOD(listener_t, ike_state_change, bool,
  266: 	NMStrongswanPluginPrivate *this, ike_sa_t *ike_sa, ike_sa_state_t state)
  267: {
  268: 	if (this->ike_sa == ike_sa && state == IKE_DESTROYING)
  269: 	{
  270: 		signal_failure(this->plugin, NM_VPN_PLUGIN_FAILURE_LOGIN_FAILED);
  271: 	}
  272: 	return TRUE;
  273: }
  274: 
  275: METHOD(listener_t, child_state_change, bool,
  276: 	NMStrongswanPluginPrivate *this, ike_sa_t *ike_sa, child_sa_t *child_sa,
  277: 	child_sa_state_t state)
  278: {
  279: 	if (this->ike_sa == ike_sa && state == CHILD_DESTROYING)
  280: 	{
  281: 		signal_failure(this->plugin, NM_VPN_PLUGIN_FAILURE_CONNECT_FAILED);
  282: 	}
  283: 	return TRUE;
  284: }
  285: 
  286: METHOD(listener_t, ike_rekey, bool,
  287: 	NMStrongswanPluginPrivate *this, ike_sa_t *old, ike_sa_t *new)
  288: {
  289: 	if (this->ike_sa == old)
  290: 	{	/* follow a rekeyed IKE_SA */
  291: 		this->ike_sa = new;
  292: 	}
  293: 	return TRUE;
  294: }
  295: 
  296: METHOD(listener_t, ike_reestablish_pre, bool,
  297: 	NMStrongswanPluginPrivate *this, ike_sa_t *old, ike_sa_t *new)
  298: {
  299: 	if (this->ike_sa == old)
  300: 	{	/* ignore child state changes during redirects etc. (task migration) */
  301: 		this->listener.child_state_change = NULL;
  302: 	}
  303: 	return TRUE;
  304: }
  305: 
  306: METHOD(listener_t, ike_reestablish_post, bool,
  307: 	NMStrongswanPluginPrivate *this, ike_sa_t *old, ike_sa_t *new,
  308: 	bool initiated)
  309: {
  310: 	if (this->ike_sa == old && initiated)
  311: 	{	/* if we get redirected during IKE_AUTH we just migrate to the new SA */
  312: 		this->ike_sa = new;
  313: 		/* re-register hooks to detect initiation failures */
  314: 		this->listener.ike_state_change = _ike_state_change;
  315: 		this->listener.child_state_change = _child_state_change;
  316: 	}
  317: 	return TRUE;
  318: }
  319: 
  320: METHOD(listener_t, child_updown, bool,
  321: 	NMStrongswanPluginPrivate *this, ike_sa_t *ike_sa, child_sa_t *child_sa,
  322: 	bool up)
  323: {
  324: 	if (this->ike_sa == ike_sa && up)
  325: 	{
  326: 		/* disable initiate-failure-detection hooks */
  327: 		this->listener.ike_state_change = NULL;
  328: 		this->listener.child_state_change = NULL;
  329: 		signal_ip_config(this->plugin, ike_sa, child_sa);
  330: 	}
  331: 	return TRUE;
  332: }
  333: 
  334: /**
  335:  * Find a certificate for which we have a private key on a smartcard
  336:  */
  337: static identification_t *find_smartcard_key(NMStrongswanPluginPrivate *priv,
  338: 											char *pin)
  339: {
  340: 	enumerator_t *enumerator, *sans;
  341: 	identification_t *id = NULL;
  342: 	certificate_t *cert;
  343: 	x509_t *x509;
  344: 	private_key_t *key;
  345: 	chunk_t keyid;
  346: 
  347: 	enumerator = lib->credmgr->create_cert_enumerator(lib->credmgr,
  348: 											CERT_X509, KEY_ANY, NULL, FALSE);
  349: 	while (enumerator->enumerate(enumerator, &cert))
  350: 	{
  351: 		x509 = (x509_t*)cert;
  352: 
  353: 		/* there might be a lot of certificates, filter them by usage */
  354: 		if ((x509->get_flags(x509) & X509_CLIENT_AUTH) &&
  355: 			!(x509->get_flags(x509) & X509_CA))
  356: 		{
  357: 			keyid = x509->get_subjectKeyIdentifier(x509);
  358: 			if (keyid.ptr)
  359: 			{
  360: 				/* try to find a private key by the certificate keyid */
  361: 				priv->creds->set_pin(priv->creds, keyid, pin);
  362: 				key = lib->creds->create(lib->creds, CRED_PRIVATE_KEY,
  363: 								KEY_ANY, BUILD_PKCS11_KEYID, keyid, BUILD_END);
  364: 				if (key)
  365: 				{
  366: 					/* prefer a more convenient subjectAltName */
  367: 					sans = x509->create_subjectAltName_enumerator(x509);
  368: 					if (!sans->enumerate(sans, &id))
  369: 					{
  370: 						id = cert->get_subject(cert);
  371: 					}
  372: 					id = id->clone(id);
  373: 					sans->destroy(sans);
  374: 
  375: 					DBG1(DBG_CFG, "using smartcard certificate '%Y'", id);
  376: 					priv->creds->set_cert_and_key(priv->creds,
  377: 												  cert->get_ref(cert), key);
  378: 					break;
  379: 				}
  380: 			}
  381: 		}
  382: 	}
  383: 	enumerator->destroy(enumerator);
  384: 	return id;
  385: }
  386: 
  387: /**
  388:  * Add a client auth config for certificate authentication
  389:  */
  390: static bool add_auth_cfg_cert(NMStrongswanPluginPrivate *priv,
  391: 							  NMSettingVpn *vpn, peer_cfg_t *peer_cfg,
  392: 							  GError **err)
  393: {
  394: 	identification_t *id = NULL;
  395: 	certificate_t *cert = NULL;
  396: 	auth_cfg_t *auth;
  397: 	const char *str, *method, *cert_source;
  398: 
  399: 	method = nm_setting_vpn_get_data_item(vpn, "method");
  400: 	cert_source = nm_setting_vpn_get_data_item(vpn, "cert-source") ?: method;
  401: 
  402: 	if (streq(cert_source, "smartcard"))
  403: 	{
  404: 		char *pin;
  405: 
  406: 		pin = (char*)nm_setting_vpn_get_secret(vpn, "password");
  407: 		if (pin)
  408: 		{
  409: 			id = find_smartcard_key(priv, pin);
  410: 		}
  411: 		if (!id)
  412: 		{
  413: 			g_set_error(err, NM_VPN_PLUGIN_ERROR,
  414: 						NM_VPN_PLUGIN_ERROR_BAD_ARGUMENTS,
  415: 						"No usable smartcard certificate found.");
  416: 			return FALSE;
  417: 		}
  418: 	}
  419: 	/* ... or certificate/private key authentication */
  420: 	else if ((str = nm_setting_vpn_get_data_item(vpn, "usercert")))
  421: 	{
  422: 		public_key_t *public;
  423: 		private_key_t *private = NULL;
  424: 
  425: 		bool agent = streq(cert_source, "agent");
  426: 
  427: 		cert = lib->creds->create(lib->creds, CRED_CERTIFICATE, CERT_X509,
  428: 								  BUILD_FROM_FILE, str, BUILD_END);
  429: 		if (!cert)
  430: 		{
  431: 			g_set_error(err, NM_VPN_PLUGIN_ERROR,
  432: 						NM_VPN_PLUGIN_ERROR_BAD_ARGUMENTS,
  433: 						"Loading peer certificate failed.");
  434: 			return FALSE;
  435: 		}
  436: 		/* try agent */
  437: 		str = nm_setting_vpn_get_secret(vpn, "agent");
  438: 		if (agent && str)
  439: 		{
  440: 			public = cert->get_public_key(cert);
  441: 			if (public)
  442: 			{
  443: 				private = lib->creds->create(lib->creds, CRED_PRIVATE_KEY,
  444: 											 public->get_type(public),
  445: 											 BUILD_AGENT_SOCKET, str,
  446: 											 BUILD_PUBLIC_KEY, public,
  447: 											 BUILD_END);
  448: 				public->destroy(public);
  449: 			}
  450: 			if (!private)
  451: 			{
  452: 				g_set_error(err, NM_VPN_PLUGIN_ERROR,
  453: 							NM_VPN_PLUGIN_ERROR_BAD_ARGUMENTS,
  454: 							"Connecting to SSH agent failed.");
  455: 			}
  456: 		}
  457: 		/* ... or key file */
  458: 		str = nm_setting_vpn_get_data_item(vpn, "userkey");
  459: 		if (!agent && str)
  460: 		{
  461: 			char *secret;
  462: 
  463: 			secret = (char*)nm_setting_vpn_get_secret(vpn, "password");
  464: 			if (secret)
  465: 			{
  466: 				priv->creds->set_key_password(priv->creds, secret);
  467: 			}
  468: 			private = lib->creds->create(lib->creds, CRED_PRIVATE_KEY,
  469: 									KEY_ANY, BUILD_FROM_FILE, str, BUILD_END);
  470: 			if (!private)
  471: 			{
  472: 				g_set_error(err, NM_VPN_PLUGIN_ERROR,
  473: 							NM_VPN_PLUGIN_ERROR_BAD_ARGUMENTS,
  474: 							"Loading private key failed.");
  475: 			}
  476: 		}
  477: 		if (private)
  478: 		{
  479: 			id = cert->get_subject(cert);
  480: 			id = id->clone(id);
  481: 			priv->creds->set_cert_and_key(priv->creds, cert, private);
  482: 		}
  483: 		else
  484: 		{
  485: 			DESTROY_IF(cert);
  486: 			return FALSE;
  487: 		}
  488: 	}
  489: 	else
  490: 	{
  491: 		g_set_error(err, NM_VPN_PLUGIN_ERROR, NM_VPN_PLUGIN_ERROR_BAD_ARGUMENTS,
  492: 					"Certificate is missing.");
  493: 		return FALSE;
  494: 	}
  495: 
  496: 	auth = auth_cfg_create();
  497: 	if (streq(method, "eap-tls"))
  498: 	{
  499: 		auth->add(auth, AUTH_RULE_AUTH_CLASS, AUTH_CLASS_EAP);
  500: 		auth->add(auth, AUTH_RULE_EAP_TYPE, EAP_TLS);
  501: 		auth->add(auth, AUTH_RULE_AAA_IDENTITY,
  502: 				  identification_create_from_string("%any"));
  503: 	}
  504: 	else
  505: 	{
  506: 		auth->add(auth, AUTH_RULE_AUTH_CLASS, AUTH_CLASS_PUBKEY);
  507: 	}
  508: 	if (cert)
  509: 	{
  510: 		auth->add(auth, AUTH_RULE_SUBJECT_CERT, cert->get_ref(cert));
  511: 	}
  512: 	str = nm_setting_vpn_get_data_item(vpn, "local-identity");
  513: 	if (str)
  514: 	{
  515: 		identification_t *local_id;
  516: 
  517: 		local_id = identification_create_from_string((char*)str);
  518: 		if (local_id)
  519: 		{
  520: 			id->destroy(id);
  521: 			id = local_id;
  522: 		}
  523: 	}
  524: 	auth->add(auth, AUTH_RULE_IDENTITY, id);
  525: 	peer_cfg->add_auth_cfg(peer_cfg, auth, TRUE);
  526: 	return TRUE;
  527: }
  528: 
  529: /**
  530:  * Add a client auth config for username/password authentication
  531:  */
  532: static bool add_auth_cfg_pw(NMStrongswanPluginPrivate *priv,
  533: 							NMSettingVpn *vpn, peer_cfg_t *peer_cfg,
  534: 							GError **err)
  535: {
  536: 	identification_t *user = NULL, *id = NULL;
  537: 	auth_cfg_t *auth;
  538: 	const char *str, *method;
  539: 
  540: 	method = nm_setting_vpn_get_data_item(vpn, "method");
  541: 
  542: 	str = nm_setting_vpn_get_data_item(vpn, "user");
  543: 	if (str)
  544: 	{
  545: 		user = identification_create_from_string((char*)str);
  546: 	}
  547: 	else
  548: 	{
  549: 		user = identification_create_from_string("%any");
  550: 	}
  551: 	str = nm_setting_vpn_get_data_item(vpn, "local-identity");
  552: 	if (str)
  553: 	{
  554: 		id = identification_create_from_string((char*)str);
  555: 	}
  556: 	else
  557: 	{
  558: 		id = user->clone(user);
  559: 	}
  560: 	str = nm_setting_vpn_get_secret(vpn, "password");
  561: 	if (streq(method, "psk"))
  562: 	{
  563: 		if (strlen(str) < 20)
  564: 		{
  565: 			g_set_error(err, NM_VPN_PLUGIN_ERROR,
  566: 						NM_VPN_PLUGIN_ERROR_BAD_ARGUMENTS,
  567: 						"Pre-shared key is too short.");
  568: 			user->destroy(user);
  569: 			id->destroy(id);
  570: 			return FALSE;
  571: 		}
  572: 		priv->creds->set_username_password(priv->creds, id, (char*)str);
  573: 	}
  574: 	else
  575: 	{
  576: 		priv->creds->set_username_password(priv->creds, user, (char*)str);
  577: 	}
  578: 
  579: 	auth = auth_cfg_create();
  580: 	auth->add(auth, AUTH_RULE_AUTH_CLASS,
  581: 			  streq(method, "psk") ? AUTH_CLASS_PSK : AUTH_CLASS_EAP);
  582: 	/* in case EAP-PEAP or EAP-TTLS is used we currently accept any identity */
  583: 	auth->add(auth, AUTH_RULE_AAA_IDENTITY,
  584: 			  identification_create_from_string("%any"));
  585: 	auth->add(auth, AUTH_RULE_EAP_IDENTITY, user);
  586: 	auth->add(auth, AUTH_RULE_IDENTITY, id);
  587: 	peer_cfg->add_auth_cfg(peer_cfg, auth, TRUE);
  588: 	return TRUE;
  589: }
  590: 
  591: /**
  592:  * Connect function called from NM via DBUS
  593:  */
  594: static gboolean connect_(NMVpnServicePlugin *plugin, NMConnection *connection,
  595: 						 GError **err)
  596: {
  597: 	NMStrongswanPlugin *pub = (NMStrongswanPlugin*)plugin;
  598: 	NMStrongswanPluginPrivate *priv;
  599: 	NMSettingConnection *conn;
  600: 	NMSettingVpn *vpn;
  601: 	enumerator_t *enumerator;
  602: 	identification_t *gateway = NULL;
  603: 	const char *str, *method;
  604: 	bool virtual, proposal;
  605: 	proposal_t *prop;
  606: 	ike_cfg_t *ike_cfg;
  607: 	peer_cfg_t *peer_cfg;
  608: 	child_cfg_t *child_cfg;
  609: 	traffic_selector_t *ts;
  610: 	ike_sa_t *ike_sa;
  611: 	auth_cfg_t *auth;
  612: 	certificate_t *cert = NULL;
  613: 	x509_t *x509;
  614: 	bool loose_gateway_id = FALSE;
  615: 	ike_cfg_create_t ike = {
  616: 		.version = IKEV2,
  617: 		.local = "%any",
  618: 		.local_port = charon->socket->get_port(charon->socket, FALSE),
  619: 		.remote_port = IKEV2_UDP_PORT,
  620: 		.fragmentation = FRAGMENTATION_YES,
  621: 	};
  622: 	peer_cfg_create_t peer = {
  623: 		.cert_policy = CERT_SEND_IF_ASKED,
  624: 		.unique = UNIQUE_REPLACE,
  625: 		.rekey_time = 36000, /* 10h */
  626: 		.jitter_time = 600, /* 10min */
  627: 		.over_time = 600, /* 10min */
  628: 	};
  629: 	child_cfg_create_t child = {
  630: 		.lifetime = {
  631: 			.time = {
  632: 				.life = 10800 /* 3h */,
  633: 				.rekey = 10200 /* 2h50min */,
  634: 				.jitter = 300 /* 5min */
  635: 			},
  636: 		},
  637: 		.mode = MODE_TUNNEL,
  638: 		.dpd_action = ACTION_RESTART,
  639: 		.close_action = ACTION_RESTART,
  640: 	};
  641: 
  642: 	/**
  643: 	 * Read parameters
  644: 	 */
  645: 	priv = NM_STRONGSWAN_PLUGIN_GET_PRIVATE(pub);
  646: 	conn = NM_SETTING_CONNECTION(nm_connection_get_setting(connection,
  647: 												NM_TYPE_SETTING_CONNECTION));
  648: 	vpn = NM_SETTING_VPN(nm_connection_get_setting(connection,
  649: 												NM_TYPE_SETTING_VPN));
  650: 	if (priv->name)
  651: 	{
  652: 		free(priv->name);
  653: 	}
  654: 	priv->name = strdup(nm_setting_connection_get_id(conn));
  655: 	DBG1(DBG_CFG, "received initiate for NetworkManager connection %s",
  656: 		 priv->name);
  657: 	DBG4(DBG_CFG, "%s",
  658: 		 nm_setting_to_string(NM_SETTING(vpn)));
  659: 	if (!priv->tun)
  660: 	{
  661: 		DBG1(DBG_CFG, "failed to create dummy TUN device, might affect DNS "
  662: 			 "server installation negatively");
  663: 	}
  664: 	ike.remote = (char*)nm_setting_vpn_get_data_item(vpn, "address");
  665: 	if (!ike.remote || !*ike.remote)
  666: 	{
  667: 		g_set_error(err, NM_VPN_PLUGIN_ERROR, NM_VPN_PLUGIN_ERROR_BAD_ARGUMENTS,
  668: 					"Gateway address missing.");
  669: 		return FALSE;
  670: 	}
  671: 	str = nm_setting_vpn_get_data_item(vpn, "server-port");
  672: 	if (str && strlen(str))
  673: 	{
  674: 		ike.remote_port = settings_value_as_int((char*)str, ike.remote_port);
  675: 	}
  676: 	str = nm_setting_vpn_get_data_item(vpn, "virtual");
  677: 	virtual = streq(str, "yes");
  678: 	str = nm_setting_vpn_get_data_item(vpn, "encap");
  679: 	ike.force_encap = streq(str, "yes");
  680: 	str = nm_setting_vpn_get_data_item(vpn, "ipcomp");
  681: 	child.options |= streq(str, "yes") ? OPT_IPCOMP : 0;
  682: 
  683: 	/**
  684: 	 * Register credentials
  685: 	 */
  686: 	priv->creds->clear(priv->creds);
  687: 
  688: 	/* gateway/CA cert */
  689: 	str = nm_setting_vpn_get_data_item(vpn, "certificate");
  690: 	if (str)
  691: 	{
  692: 		cert = lib->creds->create(lib->creds, CRED_CERTIFICATE, CERT_X509,
  693: 								  BUILD_FROM_FILE, str, BUILD_END);
  694: 		if (!cert)
  695: 		{
  696: 			g_set_error(err, NM_VPN_PLUGIN_ERROR,
  697: 						NM_VPN_PLUGIN_ERROR_BAD_ARGUMENTS,
  698: 						"Loading gateway certificate failed.");
  699: 			return FALSE;
  700: 		}
  701: 		priv->creds->add_certificate(priv->creds, cert);
  702: 	}
  703: 	else
  704: 	{
  705: 		/* no certificate defined, fall back to system-wide CA certificates */
  706: 		priv->creds->load_ca_dir(priv->creds, lib->settings->get_str(
  707: 								 lib->settings, "charon-nm.ca_dir", NM_CA_DIR));
  708: 	}
  709: 
  710: 	str = nm_setting_vpn_get_data_item(vpn, "remote-identity");
  711: 	if (str)
  712: 	{
  713: 		gateway = identification_create_from_string((char*)str);
  714: 	}
  715: 	else if (cert)
  716: 	{
  717: 		x509 = (x509_t*)cert;
  718: 		if (!(x509->get_flags(x509) & X509_CA))
  719: 		{	/* for server certificates, we use the subject as identity */
  720: 			gateway = cert->get_subject(cert);
  721: 			gateway = gateway->clone(gateway);
  722: 		}
  723: 	}
  724: 	if (!gateway || gateway->get_type(gateway) == ID_ANY)
  725: 	{
  726: 		/* if the user configured a CA certificate (or an invalid identity),
  727: 		 * we use the IP/hostname of the server */
  728: 		gateway = identification_create_from_string(ike.remote);
  729: 		loose_gateway_id = TRUE;
  730: 	}
  731: 	DBG1(DBG_CFG, "using gateway identity '%Y'", gateway);
  732: 
  733: 	/**
  734: 	 * Set up configurations
  735: 	 */
  736: 	ike_cfg = ike_cfg_create(&ike);
  737: 
  738: 	str = nm_setting_vpn_get_data_item(vpn, "proposal");
  739: 	proposal = streq(str, "yes");
  740: 	str = nm_setting_vpn_get_data_item(vpn, "ike");
  741: 	if (proposal && str && strlen(str))
  742: 	{
  743: 		enumerator = enumerator_create_token(str, ";", "");
  744: 		while (enumerator->enumerate(enumerator, &str))
  745: 		{
  746: 			prop = proposal_create_from_string(PROTO_IKE, str);
  747: 			if (!prop)
  748: 			{
  749: 				g_set_error(err, NM_VPN_PLUGIN_ERROR,
  750: 							NM_VPN_PLUGIN_ERROR_LAUNCH_FAILED,
  751: 							"Invalid IKE proposal.");
  752: 				enumerator->destroy(enumerator);
  753: 				ike_cfg->destroy(ike_cfg);
  754: 				gateway->destroy(gateway);
  755: 				return FALSE;
  756: 			}
  757: 			ike_cfg->add_proposal(ike_cfg, prop);
  758: 		}
  759: 		enumerator->destroy(enumerator);
  760: 	}
  761: 	else
  762: 	{
  763: 		ike_cfg->add_proposal(ike_cfg, proposal_create_default(PROTO_IKE));
  764: 		ike_cfg->add_proposal(ike_cfg, proposal_create_default_aead(PROTO_IKE));
  765: 	}
  766: 
  767: 	peer_cfg = peer_cfg_create(priv->name, ike_cfg, &peer);
  768: 	if (virtual)
  769: 	{
  770: 		peer_cfg->add_virtual_ip(peer_cfg, host_create_any(AF_INET));
  771: 		peer_cfg->add_virtual_ip(peer_cfg, host_create_any(AF_INET6));
  772: 	}
  773: 
  774: 	method = nm_setting_vpn_get_data_item(vpn, "method");
  775: 	if (streq(method, "cert") ||
  776: 		streq(method, "eap-tls") ||
  777: 		streq(method, "key") ||
  778: 		streq(method, "agent") ||
  779: 		streq(method, "smartcard"))
  780: 	{
  781: 		if (!add_auth_cfg_cert (priv, vpn, peer_cfg, err))
  782: 		{
  783: 			peer_cfg->destroy(peer_cfg);
  784: 			ike_cfg->destroy(ike_cfg);
  785: 			gateway->destroy(gateway);
  786: 			return FALSE;
  787: 		}
  788: 	}
  789: 	else if (streq(method, "eap") ||
  790: 			 streq(method, "psk"))
  791: 	{
  792: 		if (!add_auth_cfg_pw(priv, vpn, peer_cfg, err))
  793: 		{
  794: 			peer_cfg->destroy(peer_cfg);
  795: 			ike_cfg->destroy(ike_cfg);
  796: 			gateway->destroy(gateway);
  797: 			return FALSE;
  798: 		}
  799: 	}
  800: 	else
  801: 	{
  802: 		g_set_error(err, NM_VPN_PLUGIN_ERROR, NM_VPN_PLUGIN_ERROR_BAD_ARGUMENTS,
  803: 					"Configuration parameters missing.");
  804: 		peer_cfg->destroy(peer_cfg);
  805: 		ike_cfg->destroy(ike_cfg);
  806: 		gateway->destroy(gateway);
  807: 		return FALSE;
  808: 	}
  809: 
  810: 	auth = auth_cfg_create();
  811: 	if (streq(method, "psk"))
  812: 	{
  813: 		auth->add(auth, AUTH_RULE_AUTH_CLASS, AUTH_CLASS_PSK);
  814: 	}
  815: 	else
  816: 	{
  817: 		auth->add(auth, AUTH_RULE_AUTH_CLASS, AUTH_CLASS_PUBKEY);
  818: 	}
  819: 	auth->add(auth, AUTH_RULE_IDENTITY, gateway);
  820: 	auth->add(auth, AUTH_RULE_IDENTITY_LOOSE, loose_gateway_id);
  821: 	peer_cfg->add_auth_cfg(peer_cfg, auth, FALSE);
  822: 
  823: 	child_cfg = child_cfg_create(priv->name, &child);
  824: 	str = nm_setting_vpn_get_data_item(vpn, "esp");
  825: 	if (proposal && str && strlen(str))
  826: 	{
  827: 		enumerator = enumerator_create_token(str, ";", "");
  828: 		while (enumerator->enumerate(enumerator, &str))
  829: 		{
  830: 			prop = proposal_create_from_string(PROTO_ESP, str);
  831: 			if (!prop)
  832: 			{
  833: 				g_set_error(err, NM_VPN_PLUGIN_ERROR,
  834: 							NM_VPN_PLUGIN_ERROR_LAUNCH_FAILED,
  835: 							"Invalid ESP proposal.");
  836: 				enumerator->destroy(enumerator);
  837: 				child_cfg->destroy(child_cfg);
  838: 				peer_cfg->destroy(peer_cfg);
  839: 				return FALSE;
  840: 			}
  841: 			child_cfg->add_proposal(child_cfg, prop);
  842: 		}
  843: 		enumerator->destroy(enumerator);
  844: 	}
  845: 	else
  846: 	{
  847: 		child_cfg->add_proposal(child_cfg, proposal_create_default_aead(PROTO_ESP));
  848: 		child_cfg->add_proposal(child_cfg, proposal_create_default(PROTO_ESP));
  849: 	}
  850: 	ts = traffic_selector_create_dynamic(0, 0, 65535);
  851: 	child_cfg->add_traffic_selector(child_cfg, TRUE, ts);
  852: 	str = nm_setting_vpn_get_data_item(vpn, "remote-ts");
  853: 	if (str && strlen(str))
  854: 	{
  855: 		enumerator = enumerator_create_token(str, ";", "");
  856: 		while (enumerator->enumerate(enumerator, &str))
  857: 		{
  858: 			ts = traffic_selector_create_from_cidr((char*)str, 0, 0, 65535);
  859: 			if (!ts)
  860: 			{
  861: 				g_set_error(err, NM_VPN_PLUGIN_ERROR,
  862: 							NM_VPN_PLUGIN_ERROR_LAUNCH_FAILED,
  863: 							"Invalid remote traffic selector.");
  864: 				enumerator->destroy(enumerator);
  865: 				child_cfg->destroy(child_cfg);
  866: 				peer_cfg->destroy(peer_cfg);
  867: 				return FALSE;
  868: 			}
  869: 			child_cfg->add_traffic_selector(child_cfg, FALSE, ts);
  870: 		}
  871: 		enumerator->destroy(enumerator);
  872: 	}
  873: 	else
  874: 	{
  875: 		ts = traffic_selector_create_from_cidr("0.0.0.0/0", 0, 0, 65535);
  876: 		child_cfg->add_traffic_selector(child_cfg, FALSE, ts);
  877: 		ts = traffic_selector_create_from_cidr("::/0", 0, 0, 65535);
  878: 		child_cfg->add_traffic_selector(child_cfg, FALSE, ts);
  879: 	}
  880: 	peer_cfg->add_child_cfg(peer_cfg, child_cfg);
  881: 
  882: 	/**
  883: 	 * Prepare IKE_SA
  884: 	 */
  885: 	ike_sa = charon->ike_sa_manager->checkout_by_config(charon->ike_sa_manager,
  886: 														peer_cfg);
  887: 	peer_cfg->destroy(peer_cfg);
  888: 	if (!ike_sa)
  889: 	{
  890: 		g_set_error(err, NM_VPN_PLUGIN_ERROR, NM_VPN_PLUGIN_ERROR_LAUNCH_FAILED,
  891: 					"IKE version not supported.");
  892: 		return FALSE;
  893: 	}
  894: 
  895: 	/**
  896: 	 * Register listener, enable  initiate-failure-detection hooks
  897: 	 */
  898: 	priv->ike_sa = ike_sa;
  899: 	priv->listener.ike_state_change = _ike_state_change;
  900: 	priv->listener.child_state_change = _child_state_change;
  901: 
  902: 	/**
  903: 	 * Initiate
  904: 	 */
  905: 	child_cfg->get_ref(child_cfg);
  906: 	if (ike_sa->initiate(ike_sa, child_cfg, 0, NULL, NULL) != SUCCESS)
  907: 	{
  908: 		charon->ike_sa_manager->checkin_and_destroy(charon->ike_sa_manager, ike_sa);
  909: 
  910: 		g_set_error(err, NM_VPN_PLUGIN_ERROR, NM_VPN_PLUGIN_ERROR_LAUNCH_FAILED,
  911: 					"Initiating failed.");
  912: 		return FALSE;
  913: 	}
  914: 	charon->ike_sa_manager->checkin(charon->ike_sa_manager, ike_sa);
  915: 	return TRUE;
  916: }
  917: 
  918: /**
  919:  * NeedSecrets called from NM via DBUS
  920:  */
  921: static gboolean need_secrets(NMVpnServicePlugin *plugin, NMConnection *connection,
  922: 							 const char **setting_name, GError **error)
  923: {
  924: 	NMSettingVpn *settings;
  925: 	const char *method, *cert_source, *path;
  926: 	bool need_secret = FALSE;
  927: 
  928: 	settings = NM_SETTING_VPN(nm_connection_get_setting(connection,
  929: 														NM_TYPE_SETTING_VPN));
  930: 	method = nm_setting_vpn_get_data_item(settings, "method");
  931: 	if (method)
  932: 	{
  933: 		if (streq(method, "cert") ||
  934: 			streq(method, "eap-tls") ||
  935: 			streq(method, "key") ||
  936: 			streq(method, "agent") ||
  937: 			streq(method, "smartcard"))
  938: 		{
  939: 			cert_source = nm_setting_vpn_get_data_item(settings, "cert-source");
  940: 			if (!cert_source)
  941: 			{
  942: 				cert_source = method;
  943: 			}
  944: 			if (streq(cert_source, "agent"))
  945: 			{
  946: 				need_secret = !nm_setting_vpn_get_secret(settings, "agent");
  947: 			}
  948: 			else if (streq(cert_source, "smartcard"))
  949: 			{
  950: 				need_secret = !nm_setting_vpn_get_secret(settings, "password");
  951: 			}
  952: 			else
  953: 			{
  954: 				need_secret = TRUE;
  955: 				path = nm_setting_vpn_get_data_item(settings, "userkey");
  956: 				if (path)
  957: 				{
  958: 					private_key_t *key;
  959: 
  960: 					/* try to load/decrypt the private key */
  961: 					key = lib->creds->create(lib->creds, CRED_PRIVATE_KEY,
  962: 									KEY_ANY, BUILD_FROM_FILE, path, BUILD_END);
  963: 					if (key)
  964: 					{
  965: 						key->destroy(key);
  966: 						need_secret = FALSE;
  967: 					}
  968: 					else if (nm_setting_vpn_get_secret(settings, "password"))
  969: 					{
  970: 						need_secret = FALSE;
  971: 					}
  972: 				}
  973: 			}
  974: 		}
  975: 		else if (streq(method, "eap") ||
  976: 				 streq(method, "psk"))
  977: 		{
  978: 			need_secret = !nm_setting_vpn_get_secret(settings, "password");
  979: 		}
  980: 	}
  981: 	*setting_name = NM_SETTING_VPN_SETTING_NAME;
  982: 	return need_secret;
  983: }
  984: 
  985: /**
  986:  * The actual disconnection
  987:  */
  988: static gboolean do_disconnect(gpointer plugin)
  989: {
  990: 	NMStrongswanPluginPrivate *priv = NM_STRONGSWAN_PLUGIN_GET_PRIVATE(plugin);
  991: 	enumerator_t *enumerator;
  992: 	ike_sa_t *ike_sa;
  993: 	u_int id;
  994: 
  995: 	/* our ike_sa pointer might be invalid, lookup sa */
  996: 	enumerator = charon->controller->create_ike_sa_enumerator(
  997: 													charon->controller, TRUE);
  998: 	while (enumerator->enumerate(enumerator, &ike_sa))
  999: 	{
 1000: 		if (priv->ike_sa == ike_sa)
 1001: 		{
 1002: 			id = ike_sa->get_unique_id(ike_sa);
 1003: 			enumerator->destroy(enumerator);
 1004: 			charon->controller->terminate_ike(charon->controller, id, FALSE,
 1005: 											  controller_cb_empty, NULL, 0);
 1006: 
 1007: 			/* clear secrets as we are asked for new secrets (where we'd find
 1008: 			 * the cached secrets from earlier connections) before we clear
 1009: 			 * them in connect() */
 1010: 			priv->creds->clear(priv->creds);
 1011: 			return FALSE;
 1012: 		}
 1013: 	}
 1014: 	enumerator->destroy(enumerator);
 1015: 
 1016: 	g_debug("Connection not found.");
 1017: 	return FALSE;
 1018: }
 1019: 
 1020: /**
 1021:  * Disconnect called from NM via DBUS
 1022:  */
 1023: static gboolean disconnect(NMVpnServicePlugin *plugin, GError **err)
 1024: {
 1025: 	/* enqueue the actual disconnection, because we may be called in
 1026: 	 * response to a listener_t callback and the SA enumeration would
 1027: 	 * possibly deadlock. */
 1028: 	g_idle_add(do_disconnect, plugin);
 1029: 
 1030: 	return TRUE;
 1031: }
 1032: 
 1033: /**
 1034:  * Initializer
 1035:  */
 1036: static void nm_strongswan_plugin_init(NMStrongswanPlugin *plugin)
 1037: {
 1038: 	NMStrongswanPluginPrivate *priv;
 1039: 
 1040: 	priv = NM_STRONGSWAN_PLUGIN_GET_PRIVATE(plugin);
 1041: 	priv->plugin = NM_VPN_SERVICE_PLUGIN(plugin);
 1042: 	memset(&priv->listener, 0, sizeof(listener_t));
 1043: 	priv->listener.child_updown = _child_updown;
 1044: 	priv->listener.ike_rekey = _ike_rekey;
 1045: 	priv->listener.ike_reestablish_pre = _ike_reestablish_pre;
 1046: 	priv->listener.ike_reestablish_post = _ike_reestablish_post;
 1047: 	charon->bus->add_listener(charon->bus, &priv->listener);
 1048: 	priv->tun = tun_device_create(NULL);
 1049: 	priv->name = NULL;
 1050: }
 1051: 
 1052: /**
 1053:  * Destructor
 1054:  */
 1055: static void nm_strongswan_plugin_dispose(GObject *obj)
 1056: {
 1057: 	NMStrongswanPlugin *plugin;
 1058: 	NMStrongswanPluginPrivate *priv;
 1059: 
 1060: 	plugin = NM_STRONGSWAN_PLUGIN(obj);
 1061: 	priv = NM_STRONGSWAN_PLUGIN_GET_PRIVATE(plugin);
 1062: 	if (priv->tun)
 1063: 	{
 1064: 		priv->tun->destroy(priv->tun);
 1065: 		priv->tun = NULL;
 1066: 	}
 1067: 	G_OBJECT_CLASS (nm_strongswan_plugin_parent_class)->dispose (obj);
 1068: }
 1069: 
 1070: /**
 1071:  * Class constructor
 1072:  */
 1073: static void nm_strongswan_plugin_class_init(
 1074: 									NMStrongswanPluginClass *strongswan_class)
 1075: {
 1076: 	NMVpnServicePluginClass *parent_class = NM_VPN_SERVICE_PLUGIN_CLASS(strongswan_class);
 1077: 
 1078: 	parent_class->connect = connect_;
 1079: 	parent_class->need_secrets = need_secrets;
 1080: 	parent_class->disconnect = disconnect;
 1081: 	G_OBJECT_CLASS(strongswan_class)->dispose = nm_strongswan_plugin_dispose;
 1082: }
 1083: 
 1084: /**
 1085:  * Object constructor
 1086:  */
 1087: NMStrongswanPlugin *nm_strongswan_plugin_new(nm_creds_t *creds,
 1088: 											 nm_handler_t *handler)
 1089: {
 1090: 	GError *error = NULL;
 1091: 
 1092: 	NMStrongswanPlugin *plugin = (NMStrongswanPlugin *)g_initable_new (
 1093: 					NM_TYPE_STRONGSWAN_PLUGIN,
 1094: 					NULL,
 1095: 					&error,
 1096: 					NM_VPN_SERVICE_PLUGIN_DBUS_SERVICE_NAME, NM_DBUS_SERVICE_STRONGSWAN,
 1097: 					NULL);
 1098: 
 1099: 	if (plugin)
 1100: 	{
 1101: 		NMStrongswanPluginPrivate *priv;
 1102: 
 1103: 		/* the rest of the initialization happened in _init above */
 1104: 		priv = NM_STRONGSWAN_PLUGIN_GET_PRIVATE(plugin);
 1105: 		priv->creds = creds;
 1106: 		priv->handler = handler;
 1107: 	}
 1108: 	else
 1109: 	{
 1110: 		g_warning ("Failed to initialize a plugin instance: %s", error->message);
 1111: 		g_error_free (error);
 1112: 	}
 1113: 
 1114: 	return plugin;
 1115: }

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>