strongswan/src/libcharon/plugins/smp/smp.c

805 lines
20 KiB
C

/*
* Copyright (C) 2007 Martin Willi
* HSR Hochschule fuer Technik Rapperswil
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*/
#include <stdlib.h>
#include "smp.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include <errno.h>
#include <signal.h>
#include <inttypes.h>
#include <libxml/xmlreader.h>
#include <libxml/xmlwriter.h>
#include <library.h>
#include <daemon.h>
#include <threading/thread.h>
#include <processing/jobs/callback_job.h>
typedef struct private_smp_t private_smp_t;
/**
* Private data of an smp_t object.
*/
struct private_smp_t {
/**
* Public part of smp_t object.
*/
smp_t public;
/**
* XML unix socket fd
*/
int socket;
};
ENUM(ike_sa_state_lower_names, IKE_CREATED, IKE_DELETING,
"created",
"connecting",
"established",
"passive",
"rekeying",
"rekeyed",
"deleting",
);
/**
* write a bool into element
*/
static void write_bool(xmlTextWriterPtr writer, char *element, bool val)
{
xmlTextWriterWriteElement(writer, element, val ? "true" : "false");
}
/**
* write a identification_t into element
*/
static void write_id(xmlTextWriterPtr writer, char *element, identification_t *id)
{
xmlTextWriterStartElement(writer, element);
switch (id->get_type(id))
{
{
char *type;
while (TRUE)
{
case ID_ANY:
type = "any";
break;
case ID_IPV4_ADDR:
type = "ipv4";
break;
case ID_IPV6_ADDR:
type = "ipv6";
break;
case ID_FQDN:
type = "fqdn";
break;
case ID_RFC822_ADDR:
type = "email";
break;
case ID_DER_ASN1_DN:
type = "asn1dn";
break;
case ID_DER_ASN1_GN:
type = "asn1gn";
break;
}
xmlTextWriterWriteAttribute(writer, "type", type);
xmlTextWriterWriteFormatString(writer, "%Y", id);
break;
}
default:
/* TODO: base64 keyid */
xmlTextWriterWriteAttribute(writer, "type", "keyid");
break;
}
xmlTextWriterEndElement(writer);
}
/**
* write a host_t address into an element
*/
static void write_address(xmlTextWriterPtr writer, char *element, host_t *host)
{
xmlTextWriterStartElement(writer, element);
xmlTextWriterWriteAttribute(writer, "type",
host->get_family(host) == AF_INET ? "ipv4" : "ipv6");
if (host->is_anyaddr(host))
{ /* do not use %any for XML */
xmlTextWriterWriteFormatString(writer, "%s",
host->get_family(host) == AF_INET ? "0.0.0.0" : "::");
}
else
{
xmlTextWriterWriteFormatString(writer, "%H", host);
}
xmlTextWriterEndElement(writer);
}
/**
* write networks element
*/
static void write_networks(xmlTextWriterPtr writer, char *element,
linked_list_t *list)
{
enumerator_t *enumerator;
traffic_selector_t *ts;
xmlTextWriterStartElement(writer, element);
enumerator = list->create_enumerator(list);
while (enumerator->enumerate(enumerator, (void**)&ts))
{
xmlTextWriterStartElement(writer, "network");
xmlTextWriterWriteAttribute(writer, "type",
ts->get_type(ts) == TS_IPV4_ADDR_RANGE ? "ipv4" : "ipv6");
xmlTextWriterWriteFormatString(writer, "%R", ts);
xmlTextWriterEndElement(writer);
}
enumerator->destroy(enumerator);
xmlTextWriterEndElement(writer);
}
/**
* write a childEnd
*/
static void write_childend(xmlTextWriterPtr writer, child_sa_t *child, bool local)
{
linked_list_t *list;
xmlTextWriterWriteFormatElement(writer, "spi", "%x",
htonl(child->get_spi(child, local)));
list = linked_list_create_from_enumerator(
child->create_ts_enumerator(child, local));
write_networks(writer, "networks", list);
list->destroy(list);
}
/**
* write a child_sa_t
*/
static void write_child(xmlTextWriterPtr writer, child_sa_t *child)
{
child_cfg_t *config;
config = child->get_config(child);
xmlTextWriterStartElement(writer, "childsa");
xmlTextWriterWriteFormatElement(writer, "reqid", "%d",
child->get_reqid(child));
xmlTextWriterWriteFormatElement(writer, "childconfig", "%s",
config->get_name(config));
xmlTextWriterStartElement(writer, "local");
write_childend(writer, child, TRUE);
xmlTextWriterEndElement(writer);
xmlTextWriterStartElement(writer, "remote");
write_childend(writer, child, FALSE);
xmlTextWriterEndElement(writer);
xmlTextWriterEndElement(writer);
}
/**
* process a ikesalist query request message
*/
static void request_query_ikesa(xmlTextReaderPtr reader, xmlTextWriterPtr writer)
{
enumerator_t *enumerator;
ike_sa_t *ike_sa;
/* <ikesalist> */
xmlTextWriterStartElement(writer, "ikesalist");
enumerator = charon->controller->create_ike_sa_enumerator(
charon->controller, TRUE);
while (enumerator->enumerate(enumerator, &ike_sa))
{
ike_sa_id_t *id;
host_t *local, *remote;
enumerator_t *children;
child_sa_t *child_sa;
id = ike_sa->get_id(ike_sa);
xmlTextWriterStartElement(writer, "ikesa");
xmlTextWriterWriteFormatElement(writer, "id", "%d",
ike_sa->get_unique_id(ike_sa));
xmlTextWriterWriteFormatElement(writer, "status", "%N",
ike_sa_state_lower_names, ike_sa->get_state(ike_sa));
xmlTextWriterWriteElement(writer, "role",
id->is_initiator(id) ? "initiator" : "responder");
xmlTextWriterWriteElement(writer, "peerconfig", ike_sa->get_name(ike_sa));
/* <local> */
local = ike_sa->get_my_host(ike_sa);
xmlTextWriterStartElement(writer, "local");
xmlTextWriterWriteFormatElement(writer, "spi", "%.16"PRIx64,
be64toh(id->is_initiator(id) ? id->get_initiator_spi(id)
: id->get_responder_spi(id)));
write_id(writer, "identification", ike_sa->get_my_id(ike_sa));
write_address(writer, "address", local);
xmlTextWriterWriteFormatElement(writer, "port", "%d",
local->get_port(local));
if (ike_sa->supports_extension(ike_sa, EXT_NATT))
{
write_bool(writer, "nat", ike_sa->has_condition(ike_sa, COND_NAT_HERE));
}
xmlTextWriterEndElement(writer);
/* </local> */
/* <remote> */
remote = ike_sa->get_other_host(ike_sa);
xmlTextWriterStartElement(writer, "remote");
xmlTextWriterWriteFormatElement(writer, "spi", "%.16"PRIx64,
be64toh(id->is_initiator(id) ? id->get_responder_spi(id)
: id->get_initiator_spi(id)));
write_id(writer, "identification", ike_sa->get_other_id(ike_sa));
write_address(writer, "address", remote);
xmlTextWriterWriteFormatElement(writer, "port", "%d",
remote->get_port(remote));
if (ike_sa->supports_extension(ike_sa, EXT_NATT))
{
write_bool(writer, "nat", ike_sa->has_condition(ike_sa, COND_NAT_THERE));
}
xmlTextWriterEndElement(writer);
/* </remote> */
/* <childsalist> */
xmlTextWriterStartElement(writer, "childsalist");
children = ike_sa->create_child_sa_enumerator(ike_sa);
while (children->enumerate(children, (void**)&child_sa))
{
write_child(writer, child_sa);
}
children->destroy(children);
/* </childsalist> */
xmlTextWriterEndElement(writer);
/* </ikesa> */
xmlTextWriterEndElement(writer);
}
enumerator->destroy(enumerator);
/* </ikesalist> */
xmlTextWriterEndElement(writer);
}
/**
* process a configlist query request message
*/
static void request_query_config(xmlTextReaderPtr reader, xmlTextWriterPtr writer)
{
enumerator_t *enumerator;
peer_cfg_t *peer_cfg;
/* <configlist> */
xmlTextWriterStartElement(writer, "configlist");
enumerator = charon->backends->create_peer_cfg_enumerator(charon->backends,
NULL, NULL, NULL, NULL, IKE_ANY);
while (enumerator->enumerate(enumerator, &peer_cfg))
{
enumerator_t *children;
child_cfg_t *child_cfg;
ike_cfg_t *ike_cfg;
linked_list_t *list;
/* <peerconfig> */
xmlTextWriterStartElement(writer, "peerconfig");
xmlTextWriterWriteElement(writer, "name", peer_cfg->get_name(peer_cfg));
/* TODO: write auth_cfgs */
/* <ikeconfig> */
ike_cfg = peer_cfg->get_ike_cfg(peer_cfg);
xmlTextWriterStartElement(writer, "ikeconfig");
xmlTextWriterWriteElement(writer, "local",
ike_cfg->get_my_addr(ike_cfg));
xmlTextWriterWriteElement(writer, "remote",
ike_cfg->get_other_addr(ike_cfg));
xmlTextWriterEndElement(writer);
/* </ikeconfig> */
/* <childconfiglist> */
xmlTextWriterStartElement(writer, "childconfiglist");
children = peer_cfg->create_child_cfg_enumerator(peer_cfg);
while (children->enumerate(children, &child_cfg))
{
/* <childconfig> */
xmlTextWriterStartElement(writer, "childconfig");
xmlTextWriterWriteElement(writer, "name",
child_cfg->get_name(child_cfg));
list = child_cfg->get_traffic_selectors(child_cfg, TRUE, NULL,
NULL, FALSE);
write_networks(writer, "local", list);
list->destroy_offset(list, offsetof(traffic_selector_t, destroy));
list = child_cfg->get_traffic_selectors(child_cfg, FALSE, NULL,
NULL, FALSE);
write_networks(writer, "remote", list);
list->destroy_offset(list, offsetof(traffic_selector_t, destroy));
xmlTextWriterEndElement(writer);
/* </childconfig> */
}
children->destroy(children);
/* </childconfiglist> */
xmlTextWriterEndElement(writer);
/* </peerconfig> */
xmlTextWriterEndElement(writer);
}
enumerator->destroy(enumerator);
/* </configlist> */
xmlTextWriterEndElement(writer);
}
/**
* callback which logs to a XML writer
*/
static bool xml_callback(xmlTextWriterPtr writer, debug_t group, level_t level,
ike_sa_t* ike_sa, char* message)
{
if (level <= 1)
{
/* <item> */
xmlTextWriterStartElement(writer, "item");
xmlTextWriterWriteFormatAttribute(writer, "level", "%d", level);
xmlTextWriterWriteFormatAttribute(writer, "source", "%N", debug_names, group);
xmlTextWriterWriteFormatAttribute(writer, "thread", "%u", thread_current_id());
xmlTextWriterWriteString(writer, message);
xmlTextWriterEndElement(writer);
/* </item> */
}
return TRUE;
}
/**
* process a *terminate control request message
*/
static void request_control_terminate(xmlTextReaderPtr reader,
xmlTextWriterPtr writer, bool ike)
{
if (xmlTextReaderRead(reader) &&
xmlTextReaderNodeType(reader) == XML_READER_TYPE_TEXT)
{
const char *str;
uint32_t id;
status_t status;
str = xmlTextReaderConstValue(reader);
if (str == NULL)
{
DBG1(DBG_CFG, "error parsing XML id string");
return;
}
id = atoi(str);
if (!id)
{
enumerator_t *enumerator;
ike_sa_t *ike_sa;
enumerator = charon->controller->create_ike_sa_enumerator(
charon->controller, TRUE);
while (enumerator->enumerate(enumerator, &ike_sa))
{
if (streq(str, ike_sa->get_name(ike_sa)))
{
ike = TRUE;
id = ike_sa->get_unique_id(ike_sa);
break;
}
}
enumerator->destroy(enumerator);
}
if (!id)
{
DBG1(DBG_CFG, "error parsing XML id string");
return;
}
DBG1(DBG_CFG, "terminating %s_SA %d", ike ? "IKE" : "CHILD", id);
/* <log> */
xmlTextWriterStartElement(writer, "log");
if (ike)
{
status = charon->controller->terminate_ike(
charon->controller, id, FALSE,
(controller_cb_t)xml_callback, writer, 0);
}
else
{
status = charon->controller->terminate_child(
charon->controller, id,
(controller_cb_t)xml_callback, writer, 0);
}
/* </log> */
xmlTextWriterEndElement(writer);
xmlTextWriterWriteFormatElement(writer, "status", "%d", status);
}
}
/**
* process a *initiate control request message
*/
static void request_control_initiate(xmlTextReaderPtr reader,
xmlTextWriterPtr writer, bool ike)
{
if (xmlTextReaderRead(reader) &&
xmlTextReaderNodeType(reader) == XML_READER_TYPE_TEXT)
{
const char *str;
status_t status = FAILED;
peer_cfg_t *peer;
child_cfg_t *child = NULL;
enumerator_t *enumerator;
str = xmlTextReaderConstValue(reader);
if (str == NULL)
{
DBG1(DBG_CFG, "error parsing XML config name string");
return;
}
DBG1(DBG_CFG, "initiating %s_SA %s", ike ? "IKE" : "CHILD", str);
/* <log> */
xmlTextWriterStartElement(writer, "log");
peer = charon->backends->get_peer_cfg_by_name(charon->backends,
(char*)str);
if (peer)
{
enumerator = peer->create_child_cfg_enumerator(peer);
if (ike)
{
if (enumerator->enumerate(enumerator, &child))
{
child->get_ref(child);
}
else
{
child = NULL;
}
}
else
{
while (enumerator->enumerate(enumerator, &child))
{
if (streq(child->get_name(child), str))
{
child->get_ref(child);
break;
}
child = NULL;
}
}
enumerator->destroy(enumerator);
if (child)
{
status = charon->controller->initiate(charon->controller,
peer, child, (controller_cb_t)xml_callback,
writer, 0, FALSE);
}
else
{
peer->destroy(peer);
}
}
/* </log> */
xmlTextWriterEndElement(writer);
xmlTextWriterWriteFormatElement(writer, "status", "%d", status);
}
}
/**
* process a query request
*/
static void request_query(xmlTextReaderPtr reader, xmlTextWriterPtr writer)
{
/* <query> */
xmlTextWriterStartElement(writer, "query");
while (xmlTextReaderRead(reader))
{
if (xmlTextReaderNodeType(reader) == XML_READER_TYPE_ELEMENT)
{
if (streq(xmlTextReaderConstName(reader), "ikesalist"))
{
request_query_ikesa(reader, writer);
break;
}
if (streq(xmlTextReaderConstName(reader), "configlist"))
{
request_query_config(reader, writer);
break;
}
}
}
/* </query> */
xmlTextWriterEndElement(writer);
}
/**
* process a control request
*/
static void request_control(xmlTextReaderPtr reader, xmlTextWriterPtr writer)
{
/* <control> */
xmlTextWriterStartElement(writer, "control");
while (xmlTextReaderRead(reader))
{
if (xmlTextReaderNodeType(reader) == XML_READER_TYPE_ELEMENT)
{
if (streq(xmlTextReaderConstName(reader), "ikesaterminate"))
{
request_control_terminate(reader, writer, TRUE);
break;
}
if (streq(xmlTextReaderConstName(reader), "childsaterminate"))
{
request_control_terminate(reader, writer, FALSE);
break;
}
if (streq(xmlTextReaderConstName(reader), "ikesainitiate"))
{
request_control_initiate(reader, writer, TRUE);
break;
}
if (streq(xmlTextReaderConstName(reader), "childsainitiate"))
{
request_control_initiate(reader, writer, FALSE);
break;
}
}
}
/* </control> */
xmlTextWriterEndElement(writer);
}
/**
* process a request message
*/
static void request(xmlTextReaderPtr reader, char *id, int fd)
{
xmlTextWriterPtr writer;
writer = xmlNewTextWriter(xmlOutputBufferCreateFd(fd, NULL));
if (writer == NULL)
{
DBG1(DBG_CFG, "opening SMP XML writer failed");
return;
}
xmlTextWriterStartDocument(writer, NULL, NULL, NULL);
/* <message xmlns="http://www.strongswan.org/smp/1.0"
id="id" type="response"> */
xmlTextWriterStartElement(writer, "message");
xmlTextWriterWriteAttribute(writer, "xmlns",
"http://www.strongswan.org/smp/1.0");
xmlTextWriterWriteAttribute(writer, "id", id);
xmlTextWriterWriteAttribute(writer, "type", "response");
while (xmlTextReaderRead(reader))
{
if (xmlTextReaderNodeType(reader) == XML_READER_TYPE_ELEMENT)
{
if (streq(xmlTextReaderConstName(reader), "query"))
{
request_query(reader, writer);
break;
}
if (streq(xmlTextReaderConstName(reader), "control"))
{
request_control(reader, writer);
break;
}
}
}
/* </message> and close document */
xmlTextWriterEndDocument(writer);
xmlFreeTextWriter(writer);
}
/**
* cleanup helper function for open file descriptors
*/
static void closefdp(int *fd)
{
close(*fd);
}
/**
* read from a opened connection and process it
*/
static job_requeue_t process(int *fdp)
{
int fd = *fdp;
bool oldstate;
char buffer[4096];
ssize_t len;
xmlTextReaderPtr reader;
char *id = NULL, *type = NULL;
thread_cleanup_push((thread_cleanup_t)closefdp, (void*)&fd);
oldstate = thread_cancelability(TRUE);
len = read(fd, buffer, sizeof(buffer));
thread_cancelability(oldstate);
thread_cleanup_pop(FALSE);
if (len <= 0)
{
close(fd);
DBG2(DBG_CFG, "SMP XML connection closed");
return JOB_REQUEUE_NONE;
}
DBG3(DBG_CFG, "got XML request: %b", buffer, (u_int)len);
reader = xmlReaderForMemory(buffer, len, NULL, NULL, 0);
if (reader == NULL)
{
DBG1(DBG_CFG, "opening SMP XML reader failed");
return JOB_REQUEUE_FAIR;;
}
/* read message type and id */
while (xmlTextReaderRead(reader))
{
if (xmlTextReaderNodeType(reader) == XML_READER_TYPE_ELEMENT &&
streq(xmlTextReaderConstName(reader), "message"))
{
id = xmlTextReaderGetAttribute(reader, "id");
type = xmlTextReaderGetAttribute(reader, "type");
break;
}
}
/* process message */
if (id && type)
{
if (streq(type, "request"))
{
request(reader, id, fd);
}
else
{
/* response(reader, id) */
}
}
xmlFreeTextReader(reader);
return JOB_REQUEUE_FAIR;;
}
/**
* accept from XML socket and create jobs to process connections
*/
static job_requeue_t dispatch(private_smp_t *this)
{
struct sockaddr_un strokeaddr;
int fd, *fdp, strokeaddrlen = sizeof(strokeaddr);
callback_job_t *job;
bool oldstate;
/* wait for connections, but allow thread to terminate */
oldstate = thread_cancelability(TRUE);
fd = accept(this->socket, (struct sockaddr *)&strokeaddr, &strokeaddrlen);
thread_cancelability(oldstate);
if (fd < 0)
{
DBG1(DBG_CFG, "accepting SMP XML socket failed: %s", strerror(errno));
sleep(1);
return JOB_REQUEUE_FAIR;;
}
fdp = malloc_thing(int);
*fdp = fd;
job = callback_job_create((callback_job_cb_t)process, fdp, free,
(callback_job_cancel_t)return_false);
lib->processor->queue_job(lib->processor, (job_t*)job);
return JOB_REQUEUE_DIRECT;
}
METHOD(plugin_t, get_name, char*,
private_smp_t *this)
{
return "smp";
}
METHOD(plugin_t, get_features, int,
private_smp_t *this, plugin_feature_t *features[])
{
static plugin_feature_t f[] = {
PLUGIN_NOOP,
PLUGIN_PROVIDE(CUSTOM, "smp"),
};
*features = f;
return countof(f);
}
METHOD(plugin_t, destroy, void,
private_smp_t *this)
{
close(this->socket);
free(this);
}
/*
* Described in header file
*/
plugin_t *smp_plugin_create()
{
struct sockaddr_un unix_addr = { AF_UNIX, IPSEC_PIDDIR "/charon.xml"};
private_smp_t *this;
mode_t old;
if (!lib->caps->check(lib->caps, CAP_CHOWN))
{ /* required to chown(2) control socket */
DBG1(DBG_CFG, "smp plugin requires CAP_CHOWN capability");
return NULL;
}
INIT(this,
.public = {
.plugin = {
.get_name = _get_name,
.get_features = _get_features,
.destroy = _destroy,
},
},
);
/* set up unix socket */
this->socket = socket(AF_UNIX, SOCK_STREAM, 0);
if (this->socket == -1)
{
DBG1(DBG_CFG, "could not create XML socket");
free(this);
return NULL;
}
unlink(unix_addr.sun_path);
old = umask(S_IRWXO);
if (bind(this->socket, (struct sockaddr *)&unix_addr, sizeof(unix_addr)) < 0)
{
DBG1(DBG_CFG, "could not bind XML socket: %s", strerror(errno));
close(this->socket);
free(this);
return NULL;
}
umask(old);
if (chown(unix_addr.sun_path, lib->caps->get_uid(lib->caps),
lib->caps->get_gid(lib->caps)) != 0)
{
DBG1(DBG_CFG, "changing XML socket permissions failed: %s", strerror(errno));
}
if (listen(this->socket, 5) < 0)
{
DBG1(DBG_CFG, "could not listen on XML socket: %s", strerror(errno));
close(this->socket);
free(this);
return NULL;
}
lib->processor->queue_job(lib->processor,
(job_t*)callback_job_create_with_prio((callback_job_cb_t)dispatch, this,
NULL, (callback_job_cancel_t)return_false, JOB_PRIO_CRITICAL));
return &this->public.plugin;
}