manager can query and list IKE_SA status (no layout yet)

This commit is contained in:
Martin Willi 2007-09-13 07:45:04 +00:00
parent 28c5feddbd
commit b8c7453a82
16 changed files with 631 additions and 107 deletions

View File

@ -254,6 +254,15 @@ AC_ARG_ENABLE(
)
AM_CONDITIONAL(USE_UML, test x$uml = xtrue)
AC_ARG_ENABLE(
[manager],
AS_HELP_STRING([--enable-manager],[build web management console (default is NO).]),
[if test x$enableval = xyes; then
manager=true
fi]
)
AM_CONDITIONAL(USE_MANAGER, test x$manager = xtrue)
AC_ARG_ENABLE(
[integrity-test],
AS_HELP_STRING([--enable-integrity-test],[enable the integrity test of the crypto library (default is NO).]),
@ -371,5 +380,6 @@ AC_OUTPUT(
src/openac/Makefile
src/scepclient/Makefile
src/dumm/Makefile
src/manager/Makefile
testing/Makefile
)

View File

@ -4,3 +4,7 @@ if USE_UML
SUBDIRS += dumm
endif
if USE_MANAGER
SUBDIRS += manager
endif

View File

@ -189,6 +189,7 @@ static void request_query_ikesa(xmlTextReaderPtr reader, xmlTextWriterPtr writer
{
write_bool(writer, "nat", ike_sa->has_condition(ike_sa, COND_NAT_HERE));
}
xmlTextWriterEndElement(writer);
/* </local> */
/* <remote> */
@ -205,6 +206,7 @@ static void request_query_ikesa(xmlTextReaderPtr reader, xmlTextWriterPtr writer
{
write_bool(writer, "nat", ike_sa->has_condition(ike_sa, COND_NAT_THERE));
}
xmlTextWriterEndElement(writer);
/* </remote> */
/* <childsalist> */
@ -286,8 +288,6 @@ static void request(xmlTextReaderPtr reader, char *id, int fd)
/* </message> and close document */
xmlTextWriterEndDocument(writer);
xmlFreeTextWriter(writer);
/* write a newline to indicate end of xml */
write(fd, "\n", 1);
}
/**
@ -312,6 +312,7 @@ static job_requeue_t process(int *fdp)
DBG2(DBG_CFG, "SMP XML connection closed");
return JOB_REQUEUE_NONE;
}
DBG1(DBG_CFG, "got XML request: %b", buffer, len);
reader = xmlReaderForMemory(buffer, len, NULL, NULL, 0);
if (reader == NULL)

View File

@ -1,13 +1,13 @@
ipsec_PROGRAMS = manager
ipsec_PROGRAMS = manager.fcgi
manager_SOURCES = \
manager_fcgi_SOURCES = \
main.c manager.c manager.h gateway.h gateway.c database.h database.c \
controller/auth_controller.c controller/auth_controller.h \
controller/static_controller.c controller/static_controller.h \
controller/status_controller.c controller/status_controller.h \
controller/gateway_controller.c controller/gateway_controller.h
manager_LDADD = $(top_builddir)/src/manager/libappserv.la -lsqlite3
manager_fcgi_LDADD = $(top_builddir)/src/manager/libappserv.la -lsqlite3
@ -16,18 +16,23 @@ lib_LTLIBRARIES = libappserv.la
libappserv_la_SOURCES = \
lib/context.h lib/dispatcher.c lib/request.h lib/response.h lib/session.h \
lib/controller.h lib/dispatcher.h lib/request.c lib/response.c lib/session.c \
lib/template.h lib/template.c
lib/template.h lib/template.c lib/dict.h lib/dict.c lib/xml.h lib/xml.c
libappserv_la_LDFLAGS = -lstrongswan -lfcgi -lpthread -lneo_cs -lneo_utl
libappserv_la_LDFLAGS = -lstrongswan -lfcgi -lpthread -lneo_cs -lneo_utl ${xml_LIBS}
INCLUDES = -I$(top_srcdir)/src/libstrongswan -I$(top_srcdir)/src/manager/lib -I/usr/include/ClearSilver
INCLUDES = -I$(top_srcdir)/src/libstrongswan -I$(top_srcdir)/src/manager/lib -I/usr/include/ClearSilver ${xml_CFLAGS}
ipsec_DATA = sqlite.db
ipsec_templatesdir = ${ipsecdir}/templates
ipsec_templates_DATA = templates/header.cs templates/footer.cs
ipsec_templates_authdir = ${ipsec_templatesdir}/auth
ipsec_templates_auth_DATA = templates/auth/login.cs templates/auth/logout.cs
ipsec_templates_staticdir = ${ipsec_templatesdir}/static
ipsec_templates_static_DATA = templates/static/style.css
ipsec_templates_gatewaydir = ${ipsec_templatesdir}/gateway
ipsec_templates_gateway_DATA = templates/gateway/list.cs
ipsec_templates_statusdir = ${ipsec_templatesdir}/status
ipsec_templates_status_DATA = templates/status/ikesalist.cs

View File

@ -25,6 +25,7 @@
#include "../gateway.h"
#include <template.h>
#include <xml.h>
#include <library.h>
@ -45,9 +46,6 @@ struct private_status_controller_t {
* manager instance
*/
manager_t *manager;
int count;
};
static void ikesalist(private_status_controller_t *this,
@ -55,6 +53,9 @@ static void ikesalist(private_status_controller_t *this,
{
char *str;
gateway_t *gateway;
xml_t *doc, *node;
enumerator_t *e1, *e2, *e3, *e4, *e5, *e6;
char *name, *value, *id, *section;
gateway = this->manager->select_gateway(this->manager, 0);
str = gateway->request(gateway, "<message type=\"request\" id=\"1\">"
@ -62,13 +63,80 @@ static void ikesalist(private_status_controller_t *this,
"<ikesalist/>"
"</query>"
"</message>");
response->set_content_type(response, "text/xml");
template_t *t = template_create("templates/status/ikesalist.cs");
t->set(t, "xml", str);
t->render(t, response);
t->destroy(t);
if (str == NULL)
{
response->printf(response, "gateway did not respond");
return;
}
doc = xml_create(str);
if (doc == NULL)
{
response->printf(response, "parsing XML failed");
return;
}
template_t *t = template_create("templates/status/ikesalist.cs");
e1 = doc->children(doc);
while (e1->enumerate(e1, &node, &name, &value))
{
if (streq(name, "message"))
{
e2 = node->children(node);
while (e2->enumerate(e2, &node, &name, &value))
{
if (streq(name, "query"))
{
e3 = node->children(node);
while (e3->enumerate(e3, &node, &name, &value))
{
if (streq(name, "ikesalist"))
{
e4 = node->children(node);
while (e4->enumerate(e4, &node, &name, &value))
{
if (streq(name, "ikesa"))
{
e5 = node->children(node);
while (e5->enumerate(e5, &node, &name, &value))
{
if (streq(name, "id"))
{
id = value;
}
else if(streq(name, "local") ||
streq(name, "remote"))
{
section = name;
e6 = node->children(node);
while (e6->enumerate(e6, &node, &name, &value))
{
t->setf(t, "ikesas.%s.%s.%s=%s", id, section, name, value);
}
e6->destroy(e6);
}
else
{
t->setf(t, "ikesas.%s.%s=%s", id, name, value);
}
}
e5->destroy(e5);
}
}
e4->destroy(e4);
}
}
e3->destroy(e3);
}
}
e2->destroy(e2);
}
}
e1->destroy(e1);
t->render(t, response);
t->destroy(t);
free(str);
}
@ -128,7 +196,6 @@ controller_t *status_controller_create(context_t *context, void *param)
this->public.controller.get_handler = (controller_handler_t(*)(controller_t*,char*))get_handler;
this->public.controller.destroy = (void(*)(controller_t*))destroy;
this->count = 0;
this->manager = (manager_t*)context;
return &this->public.controller;

View File

@ -65,7 +65,7 @@ static void db_enumerator_destroy(db_enumerator_t* this)
/**
* create a database enumerator
*/
static enumerator_t *db_enumerator_create(bool(*enumerate)(db_enumerator_t*,...),
static enumerator_t *db_enumerator_create(bool(*enumerate)(db_enumerator_t*,void*,...),
sqlite3_stmt *stmt)
{
db_enumerator_t *this = malloc_thing(db_enumerator_t);
@ -78,7 +78,7 @@ static enumerator_t *db_enumerator_create(bool(*enumerate)(db_enumerator_t*,...)
/**
* enumerator function for empty enumerator
*/
static bool empty_enumerate(enumerator_t *enumerator, ...)
static bool empty_enumerate(enumerator_t *enumerator, void *item, ...)
{
return FALSE;
}
@ -120,19 +120,9 @@ static int login(private_database_t *this, char *username, char *password)
/**
* enumerate function for gateway enumrator
*/
static bool gateway_enumerate(db_enumerator_t* e, ...)
static bool gateway_enumerate(db_enumerator_t* e, int *id, const char **name,
int *port, const char **address)
{
va_list args;
int *id, *port;
const char **name, **address;
va_start(args, e);
id = va_arg(args, typeof(id));
name = va_arg(args, typeof(name));
port = va_arg(args, typeof(port));
address = va_arg(args, typeof(address));
va_end(args);
if (sqlite3_step(e->stmt) == SQLITE_ROW)
{
*id = sqlite3_column_int(e->stmt, 0);

154
src/manager/lib/dict.c Normal file
View File

@ -0,0 +1,154 @@
/**
* @file dict.c
*
* @brief Implementation of dict_t.
*
*/
/*
* Copyright (C) 2007 Martin Willi
* 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 "dict.h"
#include <utils/linked_list.h>
typedef struct private_dict_t private_dict_t;
/**
* private data of dict
*/
struct private_dict_t {
/**
* public functions
*/
dict_t public;
/**
* baaah, we really should have a hashtable for this
*/
linked_list_t *list;
/**
* key comparator function
*/
bool(*key_comparator)(void*,void*);
/**
* destructor function for key
*/
void(*key_destructor)(void*);
/**
* destructor function for value
*/
void(*value_destructor)(void*);
};
/**
* key value pair to store entries
*/
typedef struct {
void *key;
void *value;
} key_value_t;
/**
* Implementation of dict_t.get.
*/
static void* get(private_dict_t *this, void *key)
{
key_value_t *kv;
iterator_t *iterator;
void *value = NULL;
iterator = this->list->create_iterator(this->list, TRUE);
while (iterator->iterate(iterator, (void**)&kv))
{
if (this->key_comparator(kv->key, key))
{
value = kv->value;
break;
}
}
iterator->destroy(iterator);
return value;
}
/**
* Implementation of dict_t.set.
*/
static void set(private_dict_t *this, void *key, void *value)
{
/* we don't overwrite, just prepend */
key_value_t *kv = malloc_thing(key_value_t);
kv->key = key;
kv->value = value;
this->list->insert_first(this->list, kv);
}
/**
* comparator for strings
*/
bool dict_streq(void *a, void *b)
{
return streq(a, b);
}
/**
* Implementation of dict_t.destroy
*/
static void destroy(private_dict_t *this)
{
key_value_t *kv;
while (this->list->remove_last(this->list, (void**)&kv) == SUCCESS)
{
if (this->key_destructor)
{
this->key_destructor(kv->key);
}
if (this->value_destructor)
{
this->value_destructor(kv->value);
}
free(kv);
}
this->list->destroy(this->list);
free(this);
}
/*
* see header file
*/
dict_t *dict_create(bool(*key_comparator)(void*,void*),
void(*key_destructor)(void*),
void(*value_destructor)(void*))
{
private_dict_t *this = malloc_thing(private_dict_t);
this->public.set = (void(*)(dict_t*, void *key, void *value))set;
this->public.get = (void*(*)(dict_t*, void *key))get;
this->public.destroy = (void(*)(dict_t*))destroy;
this->list = linked_list_create();
this->key_comparator = key_comparator;
this->key_destructor = key_destructor;
this->value_destructor = value_destructor;
return &this->public;
}

73
src/manager/lib/dict.h Normal file
View File

@ -0,0 +1,73 @@
/**
* @file dict.h
*
* @brief Interface of dict_t.
*
*/
/*
* Copyright (C) 2007 Martin Willi
* 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.
*/
#ifndef DICT_H_
#define DICT_H_
#include <library.h>
typedef struct dict_t dict_t;
/**
* @brief Dictionary type, key value stuff.
*/
struct dict_t {
/**
* @brief Set a value in the dict.
*
* @param key key to set
* @param value value, NULL to unset key
* @return
*/
void (*set)(dict_t *this, void *key, void *value);
/**
* @brief Get a value form the dict.
*
* @param key key to get value of
* @return assigned value, NULL if not found
*/
void* (*get)(dict_t *this, void *key);
/**
* @brief Destroy a dict instance.
*/
void (*destroy)(dict_t *this);
};
/**
* @brief Key comparator function for strings
*/
bool dict_streq(void *a, void *b);
/**
* @brief Create a dict instance.
*
* @param free_key TRUE to free() keys on destruction
* @param
*/
dict_t *dict_create(bool(*key_comparator)(void*,void*),
void(*key_destructor)(void*),
void(*value_destructor)(void*));
#endif /* DICT_H_ */

View File

@ -35,10 +35,11 @@ struct enumerator_t {
/**
* @brief Enumerate collection.
*
* @param ... variable argument list of pointers, NULL terminated
* @param item first enumerated item
* @param ... additional items enumerated, depending in implementation
* @return TRUE if pointers returned
*/
bool (*enumerate)(enumerator_t *this, ...);
bool (*enumerate)(enumerator_t *this, void *item, ...);
/**
* @brief Destroy a enumerator instance.

View File

@ -26,22 +26,7 @@
#include <stdlib.h>
#include <utils/linked_list.h>
typedef struct {
char *name;
char *value;
} name_value_t;
/**
* destroy a name value pair
*/
static void name_value_destroy(name_value_t *this)
{
free(this->name);
free(this->value);
free(this);
}
#include <dict.h>
typedef struct private_request_t private_request_t;
@ -61,14 +46,14 @@ struct private_request_t {
FCGX_Request *req;
/**
* list of cookies (name_value_t)
* list of cookies
*/
linked_list_t *cookies;
dict_t *cookies;
/**
* list of post data (name_value_t)
* list of post data
*/
linked_list_t *posts;
dict_t *posts;
};
/**
@ -76,21 +61,7 @@ struct private_request_t {
*/
static char* get_cookie(private_request_t *this, char *name)
{
char *value = NULL;
name_value_t *cookie;
iterator_t *iterator;
iterator = this->cookies->create_iterator(this->cookies, TRUE);
while (iterator->iterate(iterator, (void**)&cookie))
{
if (streq(cookie->name, name))
{
value = cookie->value;
break;
}
}
iterator->destroy(iterator);
return value;
return this->cookies->get(this->cookies, name);
}
/**
@ -107,21 +78,7 @@ static char* get_path(private_request_t *this)
*/
static char* get_post_data(private_request_t *this, char *name)
{
char *value = NULL;
name_value_t *data;
iterator_t *iterator;
iterator = this->posts->create_iterator(this->posts, TRUE);
while (iterator->iterate(iterator, (void**)&data))
{
if (streq(data->name, name))
{
value = data->value;
break;
}
}
iterator->destroy(iterator);
return value;
return this->posts->get(this->posts, name);
}
/**
@ -185,7 +142,6 @@ static char *unescape(char **pos, char delimiter)
static void parse_post(private_request_t *this)
{
char buf[4096], *pos, *name, *value;
name_value_t *data;
int len;
if (!streq(FCGX_GetParam("REQUEST_METHOD", this->req->envp), "POST") ||
@ -211,10 +167,7 @@ static void parse_post(private_request_t *this)
value = unescape(&pos, '&');
if (value)
{
data = malloc_thing(name_value_t);
data->name = name;
data->value = value;
this->posts->insert_last(this->posts, data);
this->posts->set(this->posts, name, value);
continue;
}
else
@ -232,7 +185,7 @@ static void parse_post(private_request_t *this)
static void parse_cookies(private_request_t *this)
{
char *str, *pos;
name_value_t *cookie;
char *name, *value;
str = FCGX_GetParam("HTTP_COOKIE", this->req->envp);
while (str)
@ -247,23 +200,22 @@ static void parse_cookies(private_request_t *this)
{
break;
}
cookie = malloc_thing(name_value_t);
cookie->name = strndup(str, pos - str);
cookie->value = NULL;
name = strndup(str, pos - str);
value = NULL;
str = pos + 1;
if (str)
{
pos = strchr(str, ';');
if (pos)
{
cookie->value = strndup(str, pos - str);
value = strndup(str, pos - str);
}
else
{
cookie->value = strdup(str);
value = strdup(str);
}
}
this->cookies->insert_last(this->cookies, cookie);
this->cookies->set(this->cookies, name, value);
if (pos == NULL)
{
break;
@ -277,8 +229,8 @@ static void parse_cookies(private_request_t *this)
*/
static void destroy(private_request_t *this)
{
this->cookies->destroy_function(this->cookies, (void*)name_value_destroy);
this->posts->destroy_function(this->posts, (void*)name_value_destroy);
this->cookies->destroy(this->cookies);
this->posts->destroy(this->posts);
free(this);
}
@ -295,8 +247,8 @@ request_t *request_create(FCGX_Request *request)
this->public.destroy = (void(*)(request_t*))destroy;
this->req = request;
this->cookies = linked_list_create();
this->posts = linked_list_create();
this->cookies = dict_create(dict_streq, free, free);
this->posts = dict_create(dict_streq, free, free);
parse_cookies(this);
parse_post(this);

View File

@ -66,8 +66,6 @@ static void render(private_template_t *this, response_t *response)
NEOERR* err;
CSPARSE *parse;
hdf_remove_tree(this->hdf, "");
err = cs_init(&parse, this->hdf);
if (!err)
{

169
src/manager/lib/xml.c Normal file
View File

@ -0,0 +1,169 @@
/**
* @file xml.c
*
* @brief Implementation of xml_t.
*
*/
/*
* Copyright (C) 2007 Martin Willi
* 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 "xml.h"
#include <libxml/parser.h>
#include <libxml/tree.h>
typedef struct private_xml_t private_xml_t;
/**
* private data of xml
*/
struct private_xml_t {
/**
* public functions
*/
xml_t public;
/**
* root node of this xml (part)
*/
xmlNode *node;
/**
* document, only for root xml_t
*/
xmlDoc *doc;
/**
* Root xml_t*
*/
private_xml_t *root;
/**
* number of enumerator instances
*/
int enums;
};
/**
* child element enumerator
*/
typedef struct {
/** enumerator interface */
enumerator_t e;
/** current child context (returned to enumerate() caller) */
private_xml_t child;
/** currently processing node */
xmlNode *node;
} child_enum_t;
/**
* Implementation of xml_t.children().enumerate().
*/
static bool child_enumerate(child_enum_t *e, private_xml_t **child,
char **name, char **value)
{
while (e->node && e->node->type != XML_ELEMENT_NODE)
{
e->node = e->node->next;
}
if (e->node)
{
xmlNode *text;
text = e->node->children;
*value = NULL;
while (text && text->type != XML_TEXT_NODE)
{
text = text->next;
}
if (text)
{
*value = text->content;
}
*name = (char*)e->node->name;
*child = &e->child;
e->child.node = e->node->children;
e->node = e->node->next;
return TRUE;
}
return FALSE;
}
/**
* Implementation of xml_t.get_attribute.
*/
static char* get_attribute(private_xml_t *this, char *name)
{
return NULL;
}
/**
* destroy enumerator, and complete tree if this was the last enumerator
*/
static void child_destroy(child_enum_t *this)
{
if (--this->child.root->enums == 0)
{
xmlFreeDoc(this->child.root->doc);
free(this->child.root);
}
free(this);
}
/**
* Implementation of xml_t.children.
*/
static enumerator_t* children(private_xml_t *this)
{
child_enum_t *ce = malloc_thing(child_enum_t);
ce->e.enumerate = (void*)child_enumerate;
ce->e.destroy = (void*)child_destroy;
ce->node = this->node;
ce->child.public.children = (void*)children;
ce->child.public.get_attribute = (void*)get_attribute;
ce->child.node = NULL;
ce->child.doc = this->doc;
ce->child.root = this->root;
this->root->enums++;
return &ce->e;
}
/*
* see header file
*/
xml_t *xml_create(char *xml)
{
private_xml_t *this = malloc_thing(private_xml_t);
this->public.get_attribute = (char*(*)(xml_t*,char*))get_attribute;
this->public.children = (enumerator_t*(*)(xml_t*))children;
this->doc = xmlReadMemory(xml, strlen(xml), NULL, NULL, 0);
if (this->doc == NULL)
{
free(this);
return NULL;
}
this->node = xmlDocGetRootElement(this->doc);
this->root = this;
this->enums = 0;
return &this->public;
}

63
src/manager/lib/xml.h Normal file
View File

@ -0,0 +1,63 @@
/**
* @file xml.h
*
* @brief Interface of xml_t.
*
*/
/*
* Copyright (C) 2007 Martin Willi
* 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.
*/
#ifndef XML_H_
#define XML_H_
#include <enumerator.h>
typedef struct xml_t xml_t;
/**
* @brief Simple enumerator based XML parser.
*
* An xml_t is a single node of the XML tree, but also serves as root node
* and therefore the document.
* This object has no destructor, the tree gets destroyed when all enumerator
* instances get destroyed.
*/
struct xml_t {
/**
* @brief Create an enumerator over all children.
*
* Enumerated values must not be manipulated or freed.
*
* @return enumerator over (xml_t* child, char *name, char *value)
*/
enumerator_t* (*children)(xml_t *this);
/**
* @brief Get an attribute value by its name.
*
* @param name name of the attribute
* @return attribute value, NULL if not found
*/
char *(*get_attribute)(xml_t *this, char *name);
};
/**
* @brief Create a xml instance.
*/
xml_t *xml_create(char *xml);
#endif /* XML_H_ */

View File

@ -123,9 +123,12 @@ static bool login(private_manager_t *this, char *username, char *password)
*/
static void logout(private_manager_t *this)
{
if (this->gateway)
{
this->gateway->destroy(this->gateway);
this->gateway = NULL;
}
this->user = 0;
this->gateway->destroy(this->gateway);
this->gateway = NULL;
}
/**

BIN
src/manager/sqlite.db Normal file

Binary file not shown.

View File

@ -1 +1,35 @@
<?cs var:xml ?>
<?cs include:"templates/header.cs" ?>
<h1>List of IKE SA's</h1>
<table>
<tr>
<td>ID</td>
<td>Status</td>
<td>Role</td>
<td>Config</td>
<td colspan="3">Local</td>
<td colspan="3">Remote</td>
<tr>
<tr>
<td colspan="4"></td>
<td>ID</td>
<td>Address</td>
<td>SPI</td>
<td>ID</td>
<td>Address</td>
<td>SPI</td>
<tr>
<?cs each:ikesa = ikesas ?>
<td><?cs name:ikesa ?></td>
<td><?cs var:ikesa.status ?></td>
<td><?cs var:ikesa.role ?></td>
<td><?cs var:ikesa.peerconfig ?></td>
<td><?cs var:ikesa.local.identification ?></td>
<td><?cs var:ikesa.local.address ?>:<?cs var:ikesa.local.port ?></td>
<td><?cs var:ikesa.local.spi ?></td>
<td><?cs var:ikesa.remote.identification ?></td>
<td><?cs var:ikesa.remote.address ?>:<?cs var:ikesa.remote.port ?></td>
<td><?cs var:ikesa.remote.spi ?></td>
<?cs /each ?>
</tr>
</table>
<?cs include:"templates/footer.cs" ?>