From 9529fb68360212da5ef49cf30495fb8c1318aa4e Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Wed, 11 Jun 2008 14:13:24 +0000 Subject: [PATCH] added mediation server web frontend updated charons medsrv plugin to updated database scheme --- configure.in | 6 +- src/Makefile.am | 5 + src/charon/plugins/medsrv/medsrv_config.c | 10 +- src/charon/plugins/medsrv/medsrv_creds.c | 2 +- src/charon/plugins/medsrv/mysql.sql | 29 +- src/medsrv/Makefile.am | 42 +++ src/medsrv/controller/peer_controller.c | 377 +++++++++++++++++++++ src/medsrv/controller/peer_controller.h | 50 +++ src/medsrv/controller/user_controller.c | 363 ++++++++++++++++++++ src/medsrv/controller/user_controller.h | 50 +++ src/medsrv/filter/auth_filter.c | 100 ++++++ src/medsrv/filter/auth_filter.h | 50 +++ src/medsrv/main.c | 78 +++++ src/medsrv/templates/footer.cs | 4 + src/medsrv/templates/header.cs | 31 ++ src/medsrv/templates/peer/add.cs | 24 ++ src/medsrv/templates/peer/edit.cs | 25 ++ src/medsrv/templates/peer/list.cs | 28 ++ src/medsrv/templates/static/favicon.ico | Bin 0 -> 894 bytes src/medsrv/templates/static/mootools.js | 341 +++++++++++++++++++ src/medsrv/templates/static/script.js | 13 + src/medsrv/templates/static/strongswan.png | Bin 0 -> 19837 bytes src/medsrv/templates/static/style.css | 132 ++++++++ src/medsrv/templates/user/add.cs | 28 ++ src/medsrv/templates/user/edit.cs | 35 ++ src/medsrv/templates/user/help.cs | 34 ++ src/medsrv/templates/user/login.cs | 23 ++ src/medsrv/user.c | 77 +++++ src/medsrv/user.h | 52 +++ 29 files changed, 1991 insertions(+), 18 deletions(-) create mode 100644 src/medsrv/Makefile.am create mode 100755 src/medsrv/controller/peer_controller.c create mode 100755 src/medsrv/controller/peer_controller.h create mode 100755 src/medsrv/controller/user_controller.c create mode 100755 src/medsrv/controller/user_controller.h create mode 100755 src/medsrv/filter/auth_filter.c create mode 100755 src/medsrv/filter/auth_filter.h create mode 100644 src/medsrv/main.c create mode 100755 src/medsrv/templates/footer.cs create mode 100755 src/medsrv/templates/header.cs create mode 100755 src/medsrv/templates/peer/add.cs create mode 100755 src/medsrv/templates/peer/edit.cs create mode 100755 src/medsrv/templates/peer/list.cs create mode 100755 src/medsrv/templates/static/favicon.ico create mode 100644 src/medsrv/templates/static/mootools.js create mode 100644 src/medsrv/templates/static/script.js create mode 100755 src/medsrv/templates/static/strongswan.png create mode 100755 src/medsrv/templates/static/style.css create mode 100755 src/medsrv/templates/user/add.cs create mode 100755 src/medsrv/templates/user/edit.cs create mode 100644 src/medsrv/templates/user/help.cs create mode 100755 src/medsrv/templates/user/login.cs create mode 100644 src/medsrv/user.c create mode 100644 src/medsrv/user.h diff --git a/configure.in b/configure.in index 05d4db477..064d0c429 100644 --- a/configure.in +++ b/configure.in @@ -16,7 +16,7 @@ dnl =========================== dnl initialize & set some vars dnl =========================== -AC_INIT(strongSwan,4.2.4) +AC_INIT(strongSwan,4.2.4rc6) AM_INIT_AUTOMAKE(tar-ustar) AC_C_BIGENDIAN AC_SUBST(confdir, '${sysconfdir}') @@ -315,7 +315,7 @@ AC_ARG_ENABLE( AC_ARG_ENABLE( [medsrv], - AS_HELP_STRING([--enable-medsrv],[enable mediation server configuration database plugin (default is NO).]), + AS_HELP_STRING([--enable-medsrv],[enable mediation server web frontend and daemon plugin (default is NO).]), [if test x$enableval = xyes; then medsrv=true fi] @@ -589,6 +589,7 @@ fi if test x$medsrv = xtrue; then me=true + fast=true fi if test x$medcli = xtrue; then @@ -865,5 +866,6 @@ AC_OUTPUT( src/dumm/Makefile src/libfast/Makefile src/manager/Makefile + src/medsrv/Makefile testing/Makefile ) diff --git a/src/Makefile.am b/src/Makefile.am index 4407491d6..e28ed631e 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -11,6 +11,7 @@ endif if USE_PLUTO SUBDIRS += libcrypto pluto whack endif + if USE_CHARON SUBDIRS += charon endif @@ -39,6 +40,10 @@ if USE_MANAGER SUBDIRS += manager endif +if USE_MEDSRV + SUBDIRS += medsrv +endif + EXTRA_DIST = strongswan.conf install-exec-local : diff --git a/src/charon/plugins/medsrv/medsrv_config.c b/src/charon/plugins/medsrv/medsrv_config.c index 4768a2ca9..1017b9de0 100644 --- a/src/charon/plugins/medsrv/medsrv_config.c +++ b/src/charon/plugins/medsrv/medsrv_config.c @@ -85,9 +85,9 @@ static enumerator_t* create_peer_cfg_enumerator(private_medsrv_config_t *this, return NULL; } e = this->db->query(this->db, - "SELECT CONCAT(Peer.Alias, CONCAT('@', User.Login)) FROM " - "Peer JOIN User ON Peer.IdUser = User.IdUser " - "WHERE Peer.KeyID = ?", DB_BLOB, other->get_encoding(other), + "SELECT CONCAT(peer.alias, CONCAT('@', user.login)) FROM " + "peer JOIN user ON peer.user = user.id " + "WHERE peer.keyid = ?", DB_BLOB, other->get_encoding(other), DB_TEXT); if (e) { @@ -137,8 +137,8 @@ medsrv_config_t *medsrv_config_create(database_t *db) this->db = db; this->rekey = lib->settings->get_int(lib->settings, - "medmanager.rekey", 20) * 60; - this->dpd = lib->settings->get_int(lib->settings, "medmanager.dpd", 300); + "medsrv.rekey", 20) * 60; + this->dpd = lib->settings->get_int(lib->settings, "medsrv.dpd", 300); this->ike = ike_cfg_create(FALSE, FALSE, "0.0.0.0", "0.0.0.0"); this->ike->add_proposal(this->ike, proposal_create_default(PROTO_IKE)); diff --git a/src/charon/plugins/medsrv/medsrv_creds.c b/src/charon/plugins/medsrv/medsrv_creds.c index 428907628..decd38122 100644 --- a/src/charon/plugins/medsrv/medsrv_creds.c +++ b/src/charon/plugins/medsrv/medsrv_creds.c @@ -121,7 +121,7 @@ static enumerator_t* create_cert_enumerator(private_medsrv_creds_t *this, e->public.enumerate = (void*)cert_enumerator_enumerate; e->public.destroy = (void*)cert_enumerator_destroy; e->inner = this->db->query(this->db, - "SELECT PublicKey FROM Peer WHERE KeyId = ?", + "SELECT public_key FROM peer WHERE keyid = ?", DB_BLOB, id->get_encoding(id), DB_BLOB); if (!e->inner) diff --git a/src/charon/plugins/medsrv/mysql.sql b/src/charon/plugins/medsrv/mysql.sql index 0fb60dbc6..8f4ba34a9 100644 --- a/src/charon/plugins/medsrv/mysql.sql +++ b/src/charon/plugins/medsrv/mysql.sql @@ -1,12 +1,21 @@ -CREATE TABLE IF NOT EXISTS `Peer` ( - `IdPeer` int(10) unsigned NOT NULL auto_increment, - `IdUser` int(10) unsigned NOT NULL, - `Alias` varchar(30) collate utf8_unicode_ci NOT NULL, - `KeyId` varbinary(20) NOT NULL, - `PublicKey` blob NOT NULL, - PRIMARY KEY (`IdPeer`), - KEY `KeyId` (`KeyId`), - KEY `IdUser` (`IdUser`) -) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; +CREATE TABLE IF NOT EXISTS `peer` ( + `id` int(10) unsigned NOT NULL auto_increment, + `user` int(10) unsigned NOT NULL, + `alias` varchar(30) NOT NULL, + `keyid` varbinary(20) NOT NULL, + `public_key` blob, + PRIMARY KEY (`id`), + UNIQUE KEY (`user`,`alias`), + UNIQUE KEY (`keyid`), + KEY `user` (`user`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; + +CREATE TABLE IF NOT EXISTS `user` ( + `id` int(10) unsigned NOT NULL auto_increment, + `login` varchar(30) NOT NULL, + `password` varbinary(20) NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY (`login`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; diff --git a/src/medsrv/Makefile.am b/src/medsrv/Makefile.am new file mode 100644 index 000000000..1bbf3846e --- /dev/null +++ b/src/medsrv/Makefile.am @@ -0,0 +1,42 @@ +medsrvdir = ${ipsecdir}/medsrv + +medsrv_PROGRAMS = medsrv.fcgi + +medsrv_fcgi_SOURCES = user.h user.c \ +main.c filter/auth_filter.c filter/auth_filter.h \ +controller/user_controller.c controller/user_controller.h \ +controller/peer_controller.c controller/peer_controller.h + +medsrv_fcgi_LDADD = $(top_builddir)/src/libfast/libfast.la + +INCLUDES = -I$(top_srcdir)/src/libstrongswan -I$(top_srcdir)/src/libfast +AM_CFLAGS = -rdynamic \ + -DSTRONGSWAN_CONF=\"${strongswan_conf}\" \ + -DIPSECDIR=\"${ipsecdir}\" \ + -DIPSEC_PIDDIR=\"${piddir}\" \ + -DIPSEC_PLUGINDIR=\"${plugindir}\"\ + -DPLUGINS=\""${libstrongswan_plugins}\"" + +# Don't forget to add templates to EXTRA_DIST !!! How to automate? +medsrv_templatesdir = ${medsrvdir}/templates +medsrv_templates_DATA = templates/header.cs templates/footer.cs + +medsrv_templates_userdir = ${medsrv_templatesdir}/user +medsrv_templates_user_DATA = templates/user/add.cs templates/user/edit.cs \ +templates/user/login.cs templates/user/help.cs + +medsrv_templates_peerdir = ${medsrv_templatesdir}/peer +medsrv_templates_peer_DATA = templates/peer/add.cs templates/peer/edit.cs \ +templates/peer/list.cs + +medsrv_templates_staticdir = ${medsrv_templatesdir}/static +medsrv_templates_static_DATA = templates/header.cs templates/footer.cs \ +templates/static/style.css templates/static/strongswan.png \ +templates/static/favicon.ico templates/static/mootools.js + +EXTRA_DIST = templates/header.cs templates/footer.cs \ +templates/static/style.css templates/static/strongswan.png \ +templates/static/favicon.ico templates/static/mootools.js \ +templates/peer/add.cs templates/peer/edit.cs templates/peer/list.cs \ +templates/user/login.cs templates/user/add.cs templates/user/edit.cs \ +templates/user/help.cs diff --git a/src/medsrv/controller/peer_controller.c b/src/medsrv/controller/peer_controller.c new file mode 100755 index 000000000..22fc6df2f --- /dev/null +++ b/src/medsrv/controller/peer_controller.c @@ -0,0 +1,377 @@ +/* + * Copyright (C) 2008 Martin Willi + * Copyright (C) 2008 Philip Boetschi, Adrian Doerig + * 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 . + * + * 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. + * + * $Id$ + */ + +#define _GNU_SOURCE +#include + +#include "peer_controller.h" + +#include +#include +#include +#include + +typedef struct private_peer_controller_t private_peer_controller_t; + +/** + * private data of the peer_controller + */ +struct private_peer_controller_t { + + /** + * public functions + */ + peer_controller_t public; + + /** + * active user session + */ + user_t *user; + + /** + * underlying database + */ + database_t *db; +}; + +/** + * list the configured peer configs + */ +static void list(private_peer_controller_t *this, request_t *request) +{ + enumerator_t *query; + + query = this->db->query(this->db, + "SELECT id, alias, keyid FROM peer WHERE user = ? ORDER BY alias", + DB_UINT, this->user->get_user(this->user), + DB_UINT, DB_TEXT, DB_BLOB); + + if (query) + { + u_int id; + char *alias; + chunk_t keyid; + identification_t *identifier; + + while (query->enumerate(query, &id, &alias, &keyid)) + { + request->setf(request, "peers.%d.alias=%s", id, alias); + identifier = identification_create_from_encoding(ID_KEY_ID, keyid); + request->setf(request, "peers.%d.identifier=%D", id, identifier); + identifier->destroy(identifier); + } + query->destroy(query); + } + request->render(request, "templates/peer/list.cs"); +} + +/** + * verify a peer alias + */ +static bool verify_alias(private_peer_controller_t *this, request_t *request, + char *alias) +{ + if (!alias || *alias == '\0') + { + request->setf(request, "error=Alias is missing."); + return FALSE; + } + while (*alias != '\0') + { + switch (*alias) + { + case 'a' ... 'z': + case 'A' ... 'Z': + case '0' ... '9': + case '-': + case '_': + case '@': + case '.': + alias++; + continue; + default: + request->setf(request, "error=Alias invalid, " + "valid characters: A-Z a-z 0-9 - _ @ ."); + return FALSE; + } + } + return TRUE; +} + +/** + * parse and verify a public key + */ +static bool parse_public_key(private_peer_controller_t *this, + request_t *request, char *public_key, + chunk_t *encoding, chunk_t *keyid) +{ + public_key_t *public; + identification_t *id; + + if (!public_key || *public_key == '\0') + { + request->setf(request, "error=Public key is missing."); + return FALSE; + } + public = lib->creds->create(lib->creds, CRED_PUBLIC_KEY, KEY_ANY, + BUILD_BLOB_ASN1_PEM, public_key, + BUILD_END); + if (!public) + { + request->setf(request, "error=Parsing public key failed."); + return FALSE; + } + /* TODO: use get_encoding() with an encoding type */ + *encoding = asn1_wrap(ASN1_SEQUENCE, "cm", + asn1_algorithmIdentifier(OID_RSA_ENCRYPTION), + asn1_bitstring("m", public->get_encoding(public))); + id = public->get_id(public, ID_PUBKEY_SHA1); + *keyid = chunk_clone(id->get_encoding(id)); + public->destroy(public); + return TRUE; +} + +/** + * register a new peer + */ +static void add(private_peer_controller_t *this, request_t *request) +{ + char *alias = "", *public_key = ""; + + if (request->get_query_data(request, "back")) + { + return request->redirect(request, "peer/list"); + } + while (request->get_query_data(request, "add")) + { + chunk_t encoding, keyid; + + alias = request->get_query_data(request, "alias"); + public_key = request->get_query_data(request, "public_key"); + + if (!verify_alias(this, request, alias)) + { + break; + } + if (!parse_public_key(this, request, public_key, &encoding, &keyid)) + { + break; + } + if (this->db->execute(this->db, NULL, + "INSERT INTO peer (user, alias, public_key, keyid) " + "VALUES (?, ?, ?, ?)", + DB_UINT, this->user->get_user(this->user), + DB_TEXT, alias, DB_BLOB, encoding, + DB_BLOB, keyid) <= 0) + { + request->setf(request, "error=Peer already exists."); + free(keyid.ptr); + free(encoding.ptr); + break; + } + free(keyid.ptr); + free(encoding.ptr); + return request->redirect(request, "peer/list"); + } + request->set(request, "alias", alias); + request->set(request, "public_key", public_key); + + return request->render(request, "templates/peer/add.cs"); +} + +/** + * pem encode a public key into an allocated string + */ +char* pem_encode(chunk_t der) +{ + static const char *begin = "-----BEGIN PUBLIC KEY-----\n"; + static const char *end = "-----END PUBLIC KEY-----"; + size_t len; + char *pem; + chunk_t base64; + int i = 0; + + base64 = chunk_to_base64(der, NULL); + len = strlen(begin) + base64.len + base64.len/64 + strlen(end) + 2; + pem = malloc(len + 1); + + strcpy(pem, begin); + do + { + strncat(pem, base64.ptr + i, 64); + strcat(pem, "\n"); + i += 64; + } + while (i < base64.len - 2); + strcat(pem, end); + + free(base64.ptr); + return pem; +} + +/** + * edit a peer + */ +static void edit(private_peer_controller_t *this, request_t *request, int id) +{ + char *alias = "", *public_key = "", *pem; + chunk_t encoding, keyid; + + if (request->get_query_data(request, "back")) + { + return request->redirect(request, "peer/list"); + } + if (request->get_query_data(request, "delete")) + { + this->db->execute(this->db, NULL, + "DELETE FROM peer WHERE id = ? AND user = ?", + DB_INT, id, DB_UINT, this->user->get_user(this->user)); + return request->redirect(request, "peer/list"); + } + if (request->get_query_data(request, "save")) + { + while (TRUE) + { + alias = request->get_query_data(request, "alias"); + public_key = request->get_query_data(request, "public_key"); + + if (!verify_alias(this, request, alias)) + { + break; + } + if (!parse_public_key(this, request, public_key, &encoding, &keyid)) + { + break; + } + if (this->db->execute(this->db, NULL, + "UPDATE peer SET alias = ?, public_key = ?, keyid = ? " + "WHERE id = ? AND user = ?", + DB_TEXT, alias, DB_BLOB, encoding, DB_BLOB, keyid, + DB_INT, id, DB_UINT, this->user->get_user(this->user)) < 0) + { + request->setf(request, "error=Peer already exists."); + free(keyid.ptr); + free(encoding.ptr); + break; + } + free(keyid.ptr); + free(encoding.ptr); + return request->redirect(request, "peer/list"); + } + } + else + { + enumerator_t *query = this->db->query(this->db, + "SELECT alias, public_key FROM peer WHERE id = ? AND user = ?", + DB_INT, id, DB_UINT, this->user->get_user(this->user), + DB_TEXT, DB_BLOB); + if (query && query->enumerate(query, &alias, &encoding)) + { + alias = strdupa(alias); + pem = pem_encode(encoding); + public_key = strdupa(pem); + free(pem); + } + else + { + return request->redirect(request, "peer/list"); + } + DESTROY_IF(query); + } + request->set(request, "alias", alias); + request->set(request, "public_key", public_key); + return request->render(request, "templates/peer/edit.cs"); +} + +/** + * delete a peer from the database + */ +static void delete(private_peer_controller_t *this, request_t *request, int id) +{ + this->db->execute(this->db, NULL, + "DELETE FROM peer WHERE id = ? AND user = ?", + DB_INT, id, DB_UINT, this->user->get_user(this->user)); +} + +/** + * Implementation of controller_t.get_name + */ +static char* get_name(private_peer_controller_t *this) +{ + return "peer"; +} + +/** + * Implementation of controller_t.handle + */ +static void handle(private_peer_controller_t *this, request_t *request, + char *action, char *idstr) +{ + if (action) + { + int id = 0; + if (idstr) + { + id = atoi(idstr); + } + + if (streq(action, "list")) + { + return list(this, request); + } + else if (streq(action, "add")) + { + return add(this, request); + } + else if (streq(action, "edit") && id) + { + return edit(this, request, id); + } + else if (streq(action, "delete") && id) + { + delete(this, request, id); + } + } + request->redirect(request, "peer/list"); +} + +/** + * Implementation of controller_t.destroy + */ +static void destroy(private_peer_controller_t *this) +{ + free(this); +} + +/* + * see header file + */ +controller_t *peer_controller_create(user_t *user, database_t *db) +{ + private_peer_controller_t *this= malloc_thing(private_peer_controller_t); + + this->public.controller.get_name = (char*(*)(controller_t*))get_name; + this->public.controller.handle = (void(*)(controller_t*, request_t*, char*, char*, char*, char*, char*))handle; + this->public.controller.destroy = (void(*)(controller_t*))destroy; + + this->user = user; + this->db = db; + + return &this->public.controller; +} + diff --git a/src/medsrv/controller/peer_controller.h b/src/medsrv/controller/peer_controller.h new file mode 100755 index 000000000..511265487 --- /dev/null +++ b/src/medsrv/controller/peer_controller.h @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2008 Martin Willi + * Copyright (C) 2008 Philip Boetschi, Adrian Doerig + * 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 . + * + * 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. + * + * $Id$ + */ + +/** + * @defgroup peer_controller_server peer_controller + * @{ @ingroup controller_server + */ + +#ifndef PEER_CONTROLLER_H_ +#define PEER_CONTROLLER_H_ + +#include + +#include +#include + +typedef struct peer_controller_t peer_controller_t; + +/** + * Peer controller. Manages peers associated to a user. + */ +struct peer_controller_t { + + /** + * Implements controller_t interface. + */ + controller_t controller; +}; + +/** + * Create a peer_controller controller instance. + */ +controller_t *peer_controller_create(user_t *user, database_t *db); + +#endif /* PEER_CONTROLLER_H_ @} */ diff --git a/src/medsrv/controller/user_controller.c b/src/medsrv/controller/user_controller.c new file mode 100755 index 000000000..9e6d12340 --- /dev/null +++ b/src/medsrv/controller/user_controller.c @@ -0,0 +1,363 @@ +/* + * Copyright (C) 2008 Martin Willi + * Copyright (C) 2008 Philip Boetschi, Adrian Doerig + * 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 . + * + * 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. + * + * $Id$ + */ + +#define _GNU_SOURCE +#include + +#include "user_controller.h" + +#include + +typedef struct private_user_controller_t private_user_controller_t; + +/** + * private data of the user_controller + */ +struct private_user_controller_t { + + /** + * public functions + */ + user_controller_t public; + + /** + * database connection + */ + database_t *db; + + /** + * user session + */ + user_t *user; + + /** + * minimum required password lenght + */ + u_int password_length; +}; + +/** + * hash the password for database storage + */ +static chunk_t hash_password(char *login, char *password) +{ + hasher_t *hasher; + chunk_t hash, data; + + hasher = lib->crypto->create_hasher(lib->crypto, HASH_SHA1); + if (!hasher) + { + return chunk_empty; + } + data = chunk_cata("cc", chunk_create(login, strlen(login)), + chunk_create(password, strlen(password))); + hasher->allocate_hash(hasher, data, &hash); + hasher->destroy(hasher); + return hash; +} + +/** + * Login a user. + */ +static void login(private_user_controller_t *this, request_t *request) +{ + if (request->get_query_data(request, "submit")) + { + char *login, *password; + + login = request->get_query_data(request, "login"); + password = request->get_query_data(request, "password"); + + if (login && password) + { + enumerator_t *query; + u_int id = 0; + chunk_t hash; + + hash = hash_password(login, password); + query = this->db->query(this->db, + "SELECT id FROM user WHERE login = ? AND password = ?", + DB_TEXT, login, DB_BLOB, hash, DB_UINT); + if (query) + { + query->enumerate(query, &id); + query->destroy(query); + } + free(hash.ptr); + if (id) + { + this->user->set_user(this->user, id); + return request->redirect(request, "peer/list"); + } + } + request->setf(request, "error=Invalid username or password."); + } + request->render(request, "templates/user/login.cs"); +} + +/** + * Logout a user. + */ +static void logout(private_user_controller_t *this, request_t *request) +{ + request->redirect(request, "user/login"); + request->close_session(request); +} + +/** + * verify a user entered username for validity + */ +static bool verify_login(private_user_controller_t *this, request_t *request, + char *login) +{ + if (!login || *login == '\0') + { + request->setf(request, "error=Username is missing."); + return FALSE; + } + while (*login != '\0') + { + switch (*login) + { + case 'a' ... 'z': + case 'A' ... 'Z': + case '0' ... '9': + case '-': + case '_': + case '@': + case '.': + login++; + continue; + default: + request->setf(request, "error=Username invalid, " + "valid characters: A-Z a-z 0-9 - _ @ ."); + } + } + return TRUE; +} + +/** + * verify a user entered password for validity + */ +static bool verify_password(private_user_controller_t *this, request_t *request, + char *password, char *confirm) +{ + if (!password || *password == '\0') + { + request->setf(request, "error=Password is missing."); + return FALSE; + } + if (strlen(password) < this->password_length) + { + request->setf(request, "error=Password requires at least %d characters.", + this->password_length); + return FALSE; + } + if (!confirm || !streq(password, confirm)) + { + request->setf(request, "error=Password not confirmed."); + return FALSE; + } + return TRUE; +} + +/** + * Register a user. + */ +static void add(private_user_controller_t *this, request_t *request) +{ + char *login = ""; + + while (request->get_query_data(request, "register")) + { + char *password, *confirm; + chunk_t hash; + u_int id; + + login = request->get_query_data(request, "new_login"); + password = request->get_query_data(request, "new_password"); + confirm = request->get_query_data(request, "confirm_password"); + + if (!verify_login(this, request, login) || + !verify_password(this, request, password, confirm)) + { + break; + } + + hash = hash_password(login, password); + if (!hash.ptr || this->db->execute(this->db, &id, + "INSERT INTO user (login, password) VALUES (?, ?)", + DB_TEXT, login, DB_BLOB, hash) < 0) + { + request->setf(request, "error=Username already exists."); + free(hash.ptr); + break; + } + free(hash.ptr); + this->user->set_user(this->user, id); + return request->redirect(request, "peer/list"); + } + request->set(request, "new_login", login); + request->setf(request, "password_length=%d", this->password_length); + request->render(request, "templates/user/add.cs"); +} + +/** + * Edit the logged in user + */ +static void edit(private_user_controller_t *this, request_t *request) +{ + enumerator_t *query; + char *old_login; + + /* lookup old login */ + query = this->db->query(this->db, "SELECT login FROM user WHERE id = ?", + DB_INT, this->user->get_user(this->user), + DB_TEXT); + if (!query || !query->enumerate(query, &old_login)) + { + DESTROY_IF(query); + request->close_session(request); + return request->redirect(request, "user/login"); + } + old_login = strdupa(old_login); + query->destroy(query); + + /* back pressed */ + if (request->get_query_data(request, "back")) + { + return request->redirect(request, "peer/list"); + } + /* delete pressed */ + if (request->get_query_data(request, "delete")) + { + this->db->execute(this->db, NULL, "DELETE FROM user WHERE id = ?", + DB_UINT, this->user->get_user(this->user)); + this->db->execute(this->db, NULL, + "DELETE FROM peer WHERE user = ?", + DB_UINT, this->user->get_user(this->user)); + return logout(this, request); + } + /* save pressed */ + while (request->get_query_data(request, "save")) + { + char *new_login, *old_pass, *new_pass, *confirm; + chunk_t old_hash, new_hash; + + new_login = request->get_query_data(request, "old_login"); + old_pass = request->get_query_data(request, "old_password"); + new_pass = request->get_query_data(request, "new_password"); + confirm = request->get_query_data(request, "confirm_password"); + + if (!verify_login(this, request, new_login) || + !verify_password(this, request, new_pass, confirm)) + { + old_login = new_login; + break; + } + old_hash = hash_password(old_login, old_pass); + new_hash = hash_password(new_login, new_pass); + + if (this->db->execute(this->db, NULL, + "UPDATE user SET login = ?, password = ? " + "WHERE id = ? AND password = ?", + DB_TEXT, new_login, DB_BLOB, new_hash, + DB_UINT, this->user->get_user(this->user), DB_BLOB, old_hash) <= 0) + { + free(new_hash.ptr); + free(old_hash.ptr); + old_login = new_login; + request->setf(request, "error=Password verification failed."); + break; + } + free(new_hash.ptr); + free(old_hash.ptr); + return request->redirect(request, "peer/list"); + } + /* on error/template rendering */ + request->set(request, "old_login", old_login); + request->setf(request, "password_length=%d", this->password_length); + request->render(request, "templates/user/edit.cs"); +} + +/** + * Implementation of controller_t.get_name + */ +static char* get_name(private_user_controller_t *this) +{ + return "user"; +} + +/** + * Implementation of controller_t.handle + */ +static void handle(private_user_controller_t *this, request_t *request, char *action) +{ + if (action) + { + if (streq(action, "add")) + { + return add(this, request); + } + if (streq(action, "login")) + { + return login(this, request); + } + else if (streq(action, "logout")) + { + return logout(this, request); + } + else if (streq(action, "edit")) + { + return edit(this, request); + } + else if (streq(action, "help")) + { + return request->render(request, "templates/user/help.cs"); + } + } + request->redirect(request, "user/login"); +} + +/** + * Implementation of controller_t.destroy + */ +static void destroy(private_user_controller_t *this) +{ + free(this); +} + +/* + * see header file + */ +controller_t *user_controller_create(user_t *user, database_t *db) +{ + private_user_controller_t *this= malloc_thing(private_user_controller_t); + + this->public.controller.get_name = (char*(*)(controller_t*))get_name; + this->public.controller.handle = (void(*)(controller_t*, request_t*, char*, char*, char*, char*, char*))handle; + this->public.controller.destroy = (void(*)(controller_t*))destroy; + + this->user = user; + this->db = db; + this->password_length = lib->settings->get_int(lib->settings, + "medsrv.password_length", 6); + + return &this->public.controller; +} + diff --git a/src/medsrv/controller/user_controller.h b/src/medsrv/controller/user_controller.h new file mode 100755 index 000000000..897e28362 --- /dev/null +++ b/src/medsrv/controller/user_controller.h @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2008 Martin Willi + * Copyright (C) 2008 Philip Boetschi, Adrian Doerig + * 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 . + * + * 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. + * + * $Id$ + */ + +/** + * @defgroup user_controller_server user_controller + * @{ @ingroup controller_server + */ + +#ifndef USER_CONTROLLER_H_ +#define USER_CONTROLLER_H_ + +#include + +#include +#include + +typedef struct user_controller_t user_controller_t; + +/** + * User controller. Register, Login and user management. + */ +struct user_controller_t { + + /** + * Implements controller_t interface. + */ + controller_t controller; +}; + +/** + * Create a user_controller controller instance. + */ +controller_t *user_controller_create(user_t *user, database_t *db); + +#endif /* USER_CONTROLLER_H_ @} */ diff --git a/src/medsrv/filter/auth_filter.c b/src/medsrv/filter/auth_filter.c new file mode 100755 index 000000000..5036d26f1 --- /dev/null +++ b/src/medsrv/filter/auth_filter.c @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2008 Martin Willi + * Copyright (C) 2008 Philip Boetschi, Adrian Doerig + * 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 . + * + * 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. + * + * $Id$ + */ + +#include "auth_filter.h" + +#include + +typedef struct private_auth_filter_t private_auth_filter_t; + +/** + * private data of auth_filter + */ +struct private_auth_filter_t { + /** + * public functions + */ + auth_filter_t public; + + /** + * user session + */ + user_t *user; + + /** + * database connection + */ + database_t *db; +}; + +/** + * Implementation of filter_t.run + */ +static bool run(private_auth_filter_t *this, request_t *request, + char *controller, char *action) +{ + if (this->user->get_user(this->user)) + { + enumerator_t *query; + char *login; + + query = this->db->query(this->db, "SELECT login FROM user WHERE id = ?", + DB_INT, this->user->get_user(this->user), + DB_TEXT); + if (query && query->enumerate(query, &login)) + { + request->set(request, "login", login); + query->destroy(query); + return TRUE; + } + DESTROY_IF(query); + this->user->set_user(this->user, 0); + } + if (controller && streq(controller, "user") && action && + (streq(action, "add") || streq(action, "login") || streq(action, "help"))) + { /* add/login allowed */ + return TRUE; + } + request->redirect(request, "user/login"); + return FALSE; +} + +/** + * Implementation of filter_t.destroy + */ +static void destroy(private_auth_filter_t *this) +{ + free(this); +} + +/* + * see header file + */ +filter_t *auth_filter_create(user_t *user, database_t *db) +{ + private_auth_filter_t *this= malloc_thing(private_auth_filter_t); + + this->public.filter.destroy = (void(*)(filter_t*))destroy; + this->public.filter.run = (bool(*)(filter_t*, request_t*,char*,char*,char*,char*,char*,char*))run; + + this->user = user; + this->db = db; + + return &this->public.filter; +} + diff --git a/src/medsrv/filter/auth_filter.h b/src/medsrv/filter/auth_filter.h new file mode 100755 index 000000000..5ba270e72 --- /dev/null +++ b/src/medsrv/filter/auth_filter.h @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2008 Martin Willi + * Copyright (C) 2008 Philip Boetschi, Adrian Doerig + * 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 . + * + * 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. + * + * $Id$ + */ + +/** + * @defgroup auth_filter_server auth_filter + * @{ @ingroup filter_server + */ + +#ifndef AUTH_FILTER_H_ +#define AUTH_FILTER_H_ + +#include +#include + +#include "user.h" + +typedef struct auth_filter_t auth_filter_t; + +/** + * Authentication/Authorization filter. + */ +struct auth_filter_t { + + /** + * Implements filter_t interface. + */ + filter_t filter; +}; + +/** + * Create a auth_filter instance. + */ +filter_t *auth_filter_create(user_t *user, database_t *db); + +#endif /* AUTH_FILTER_H_ @}*/ diff --git a/src/medsrv/main.c b/src/medsrv/main.c new file mode 100644 index 000000000..00975e93a --- /dev/null +++ b/src/medsrv/main.c @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2008 Martin Willi + * Copyright (C) 2008 Philip Boetschi, Adrian Doerig + * 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 . + * + * 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. + * + * $Id$ + */ + +#include + +#include +#include +#include + +#include "filter/auth_filter.h" +#include "controller/user_controller.h" +#include "controller/peer_controller.h" + +int main(int arc, char *argv[]) +{ + dispatcher_t *dispatcher; + database_t *db; + char *socket; + bool debug; + char *uri; + int timeout, threads; + + library_init(STRONGSWAN_CONF); + lib->plugins->load(lib->plugins, IPSEC_PLUGINDIR, + lib->settings->get_str(lib->settings, "medsrv.load", PLUGINS)); + + socket = lib->settings->get_str(lib->settings, "medsrv.socket", NULL); + debug = lib->settings->get_bool(lib->settings, "medsrv.debug", FALSE); + timeout = lib->settings->get_int(lib->settings, "medsrv.timeout", 900); + threads = lib->settings->get_int(lib->settings, "medsrv.threads", 5); + uri = lib->settings->get_str(lib->settings, "medsrv.database", NULL); + if (uri == NULL) + { + fprintf(stderr, "database URI medsrv.database not defined.\n"); + return 1; + } + + db = lib->db->create(lib->db, uri); + if (db == NULL) + { + fprintf(stderr, "opening database failed.\n"); + return 1; + } + + dispatcher = dispatcher_create(socket, debug, timeout, + (context_constructor_t)user_create, db); + dispatcher->add_filter(dispatcher, + (filter_constructor_t)auth_filter_create, db); + dispatcher->add_controller(dispatcher, + (controller_constructor_t)user_controller_create, db); + dispatcher->add_controller(dispatcher, + (controller_constructor_t)peer_controller_create, db); + + dispatcher->run(dispatcher, threads); + + dispatcher->waitsignal(dispatcher); + dispatcher->destroy(dispatcher); + db->destroy(db); + + library_deinit(); + return 0; +} + diff --git a/src/medsrv/templates/footer.cs b/src/medsrv/templates/footer.cs new file mode 100755 index 000000000..db3601961 --- /dev/null +++ b/src/medsrv/templates/footer.cs @@ -0,0 +1,4 @@ + + + + diff --git a/src/medsrv/templates/header.cs b/src/medsrv/templates/header.cs new file mode 100755 index 000000000..4ab4afd1e --- /dev/null +++ b/src/medsrv/templates/header.cs @@ -0,0 +1,31 @@ + + + + strongSwan Mediation Service + + + + + + +
+ + + +

Mediation Service

+
+ +
+
+
diff --git a/src/medsrv/templates/peer/add.cs b/src/medsrv/templates/peer/add.cs new file mode 100755 index 000000000..28a994f7f --- /dev/null +++ b/src/medsrv/templates/peer/add.cs @@ -0,0 +1,24 @@ + +
+ +
+ + + + + + + + + + + + + + +
+ + +
+
+ diff --git a/src/medsrv/templates/peer/edit.cs b/src/medsrv/templates/peer/edit.cs new file mode 100755 index 000000000..76fb9dafc --- /dev/null +++ b/src/medsrv/templates/peer/edit.cs @@ -0,0 +1,25 @@ + +
+ +
+ + + + + + + + + + + + + + +
+ + + +
+
+ diff --git a/src/medsrv/templates/peer/list.cs b/src/medsrv/templates/peer/list.cs new file mode 100755 index 000000000..205452641 --- /dev/null +++ b/src/medsrv/templates/peer/list.cs @@ -0,0 +1,28 @@ + +
+ 0 ?> + + + + + + + + + + + +
AliasKey Identifier
+ + + +
+ +No peers defined. + +
+
+ +
+
+ diff --git a/src/medsrv/templates/static/favicon.ico b/src/medsrv/templates/static/favicon.ico new file mode 100755 index 0000000000000000000000000000000000000000..d004591963d5f531ea7bbf3be030d11360a3fd22 GIT binary patch literal 894 zcmZQzU<5(|0R|u`!H~hsz#zuJz@P!dKp_SNAO?x!0s&Stbft0)TmpwO*)=e{9Tr#hmAqt;$qe<3tAsCm^y`YcghhV9fZiEbRHOpa0-Gfa>ew zp976DVSH%9bk~&kkb_y0i;Ii1gAb4m6c1E;^5otxWc5uc&rO*g0;Ry{fvv(KKi@z< zKYuIv89)&S!ABR4en(bc7Y8yQj%>w_Cd6eWCnq~-P5=tp@I2bT{wp5!K3d0`8af&p z8g1m37&APw=YM?W08aC*xbAmXAKJ8ObA45>3HwE$`aspEPwxH3YQBTO!)XnVpFMkZ z=FB-at(8Ew@J`T}(wrs*DWc9A%k8fZ42^487I%UFq7pNG5tT-Rd zsQ+;5;!k)K1I@2bcr?waJ-YK1KWZ;NjVoUe GD-Hl6bl2nn literal 0 HcmV?d00001 diff --git a/src/medsrv/templates/static/mootools.js b/src/medsrv/templates/static/mootools.js new file mode 100644 index 000000000..d953a1c06 --- /dev/null +++ b/src/medsrv/templates/static/mootools.js @@ -0,0 +1,341 @@ +//MooTools, , My Object Oriented (JavaScript) Tools. Copyright (c) 2006-2008 Valerio Proietti, , MIT Style License. + +var MooTools={version:"1.2dev",build:""};var Native=function(J){J=J||{};var F=J.afterImplement||function(){};var G=J.generics;G=(G!==false);var H=J.legacy; +var E=J.initialize;var B=J.protect;var A=J.name;var C=E||H;C.constructor=Native;C.$family={name:"native"};if(H&&E){C.prototype=H.prototype;}C.prototype.constructor=C; +if(A){var D=A.toLowerCase();C.prototype.$family={name:D};Native.typize(C,D);}var I=function(M,K,N,L){if(!B||L||!M.prototype[K]){M.prototype[K]=N;}if(G){Native.genericize(M,K,B); +}F.call(M,K,N);return M;};C.implement=function(L,K,N){if(typeof L=="string"){return I(this,L,K,N);}for(var M in L){I(this,M,L[M],K);}return this;};C.alias=function(M,K,N){if(typeof M=="string"){M=this.prototype[M]; +if(M){I(this,K,M,N);}}else{for(var L in M){this.alias(L,M[L],K);}}return this;};return C;};Native.implement=function(D,C){for(var B=0,A=D.length;B-1:this.indexOf(A)>-1;},trim:function(){return this.replace(/^\s+|\s+$/g,"");},clean:function(){return this.replace(/\s+/g," ").trim(); +},camelCase:function(){return this.replace(/-\D/g,function(A){return A.charAt(1).toUpperCase();});},hyphenate:function(){return this.replace(/[A-Z]/g,function(A){return("-"+A.charAt(0).toLowerCase()); +});},capitalize:function(){return this.replace(/\b[a-z]/g,function(A){return A.toUpperCase();});},escapeRegExp:function(){return this.replace(/([-.*+?^${}()|[\]\/\\])/g,"\\$1"); +},toInt:function(A){return parseInt(this,A||10);},toFloat:function(){return parseFloat(this);},hexToRgb:function(B){var A=this.match(/^#?(\w{1,2})(\w{1,2})(\w{1,2})$/); +return(A)?A.slice(1).hexToRgb(B):null;},rgbToHex:function(B){var A=this.match(/\d{1,3}/g);return(A)?A.rgbToHex(B):null;},stripScripts:function(B){var A=""; +var C=this.replace(/]*>([\s\S]*?)<\/script>/gi,function(){A+=arguments[1]+"\n";return"";});if(B===true){$exec(A);}else{if($type(B)=="function"){B(A,C); +}}return C;},substitute:function(A,B){return this.replace(B||(/\\?\{([^}]+)\}/g),function(D,C){if(D.charAt(0)=="\\"){return D.slice(1);}return(A[C]!=undefined)?A[C]:""; +});}});Hash.implement({has:Object.prototype.hasOwnProperty,keyOf:function(B){for(var A in this){if(this.hasOwnProperty(A)&&this[A]===B){return A;}}return null; +},hasValue:function(A){return(Hash.keyOf(this,A)!==null);},extend:function(A){Hash.each(A,function(C,B){Hash.set(this,B,C);},this);return this;},combine:function(A){Hash.each(A,function(C,B){Hash.include(this,B,C); +},this);return this;},erase:function(A){if(this.hasOwnProperty(A)){delete this[A];}return this;},get:function(A){return(this.hasOwnProperty(A))?this[A]:null; +},set:function(A,B){if(!this[A]||this.hasOwnProperty(A)){this[A]=B;}return this;},empty:function(){Hash.each(this,function(B,A){delete this[A];},this); +return this;},include:function(B,C){var A=this[B];if(A==undefined){this[B]=C;}return this;},map:function(B,C){var A=new Hash;Hash.each(this,function(E,D){A.set(D,B.call(C,E,D,this)); +},this);return A;},filter:function(B,C){var A=new Hash;Hash.each(this,function(E,D){if(B.call(C,E,D,this)){A.set(D,E);}},this);return A;},every:function(B,C){for(var A in this){if(this.hasOwnProperty(A)&&!B.call(C,this[A],A)){return false; +}}return true;},some:function(B,C){for(var A in this){if(this.hasOwnProperty(A)&&B.call(C,this[A],A)){return true;}}return false;},getKeys:function(){var A=[]; +Hash.each(this,function(C,B){A.push(B);});return A;},getValues:function(){var A=[];Hash.each(this,function(B){A.push(B);});return A;},toQueryString:function(A){var B=[]; +Hash.each(this,function(F,E){if(A){E=A+"["+E+"]";}var D;switch($type(F)){case"object":D=Hash.toQueryString(F,E);break;case"array":var C={};F.each(function(H,G){C[G]=H; +});D=Hash.toQueryString(C,E);break;default:D=E+"="+encodeURIComponent(F);}if(F!=undefined){B.push(D);}});return B.join("&");}});Hash.alias({keyOf:"indexOf",hasValue:"contains"}); +var Event=new Native({name:"Event",initialize:function(A,F){F=F||window;var K=F.document;A=A||F.event;if(A.$extended){return A;}this.$extended=true;var J=A.type; +var G=A.target||A.srcElement;while(G&&G.nodeType==3){G=G.parentNode;}if(J.test(/key/)){var B=A.which||A.keyCode;var M=Event.Keys.keyOf(B);if(J=="keydown"){var D=B-111; +if(D>0&&D<13){M="f"+D;}}M=M||String.fromCharCode(B).toLowerCase();}else{if(J.match(/(click|mouse|menu)/i)){K=(!K.compatMode||K.compatMode=="CSS1Compat")?K.html:K.body; +var I={x:A.pageX||A.clientX+K.scrollLeft,y:A.pageY||A.clientY+K.scrollTop};var C={x:(A.pageX)?A.pageX-F.pageXOffset:A.clientX,y:(A.pageY)?A.pageY-F.pageYOffset:A.clientY}; +if(J.match(/DOMMouseScroll|mousewheel/)){var H=(A.wheelDelta)?A.wheelDelta/120:-(A.detail||0)/3;}var E=(A.which==3)||(A.button==2);var L=null;if(J.match(/over|out/)){switch(J){case"mouseover":L=A.relatedTarget||A.fromElement; +break;case"mouseout":L=A.relatedTarget||A.toElement;}if(!(function(){while(L&&L.nodeType==3){L=L.parentNode;}return true;}).create({attempt:Browser.Engine.gecko})()){L=false; +}}}}return $extend(this,{event:A,type:J,page:I,client:C,rightClick:E,wheel:H,relatedTarget:L,target:G,code:B,key:M,shift:A.shiftKey,control:A.ctrlKey,alt:A.altKey,meta:A.metaKey}); +}});Event.Keys=new Hash({enter:13,up:38,down:40,left:37,right:39,esc:27,space:32,backspace:8,tab:9,"delete":46});Event.implement({stop:function(){return this.stopPropagation().preventDefault(); +},stopPropagation:function(){if(this.event.stopPropagation){this.event.stopPropagation();}else{this.event.cancelBubble=true;}return this;},preventDefault:function(){if(this.event.preventDefault){this.event.preventDefault(); +}else{this.event.returnValue=false;}return this;}});var Class=new Native({name:"Class",initialize:function(B){B=B||{};var A=function(E){for(var D in this){this[D]=$unlink(this[D]); +}for(var F in Class.Mutators){if(!this[F]){continue;}Class.Mutators[F](this,this[F]);delete this[F];}this.constructor=A;if(E===$empty){return this;}var C=(this.initialize)?this.initialize.apply(this,arguments):this; +if(this.options&&this.options.initialize){this.options.initialize.call(this);}return C;};$extend(A,this);A.constructor=Class;A.prototype=B;return A;}}); +Class.implement({implement:function(){Class.Mutators.Implements(this.prototype,Array.slice(arguments));return this;}});Class.Mutators={Implements:function(A,B){$splat(B).each(function(C){$extend(A,($type(C)=="class")?new C($empty):C); +});},Extends:function(self,klass){var instance=new klass($empty);delete instance.parent;delete instance.parentOf;for(var key in instance){var current=self[key],previous=instance[key]; +if(current==undefined){self[key]=previous;continue;}var ctype=$type(current),ptype=$type(previous);if(ctype!=ptype){continue;}switch(ctype){case"function":if(!arguments.callee.caller){self[key]=eval("("+String(current).replace(/\bthis\.parent\(\s*(\))?/g,function(full,close){return"arguments.callee._parent_.call(this"+(close||", "); +})+")");}self[key]._parent_=previous;break;case"object":self[key]=$merge(previous,current);}}self.parent=function(){return arguments.callee.caller._parent_.apply(this,arguments); +};self.parentOf=function(descendant){return descendant._parent_.apply(this,Array.slice(arguments,1));};}};var Chain=new Class({chain:function(){this.$chain=(this.$chain||[]).extend(arguments); +return this;},callChain:function(){return(this.$chain&&this.$chain.length)?this.$chain.shift().apply(this,arguments):false;},clearChain:function(){if(this.$chain){this.$chain.empty(); +}return this;}});var Events=new Class({addEvent:function(C,B,A){C=Events.removeOn(C);if(B!=$empty){this.$events=this.$events||{};this.$events[C]=this.$events[C]||[]; +this.$events[C].include(B);if(A){B.internal=true;}}return this;},addEvents:function(A){for(var B in A){this.addEvent(B,A[B]);}return this;},fireEvent:function(C,B,A){C=Events.removeOn(C); +if(!this.$events||!this.$events[C]){return this;}this.$events[C].each(function(D){D.create({bind:this,delay:A,"arguments":B})();},this);return this;},removeEvent:function(B,A){B=Events.removeOn(B); +if(!this.$events||!this.$events[B]){return this;}if(!A.internal){this.$events[B].erase(A);}return this;},removeEvents:function(C){for(var D in this.$events){if(C&&C!=D){continue; +}var B=this.$events[D];for(var A=B.length;A--;A){this.removeEvent(D,B[A]);}}return this;}});Events.removeOn=function(A){return A.replace(/^on([A-Z])/,function(B,C){return C.toLowerCase(); +});};var Options=new Class({setOptions:function(){this.options=$merge.run([this.options].extend(arguments));if(!this.addEvent){return this;}for(var A in this.options){if($type(this.options[A])!="function"||!(/^on[A-Z]/).test(A)){continue; +}this.addEvent(A,this.options[A]);delete this.options[A];}return this;}});Document.implement({newElement:function(A,B){if(Browser.Engine.trident&&B){["name","type","checked"].each(function(C){if(!B[C]){return ; +}A+=" "+C+'="'+B[C]+'"';if(C!="checked"){delete B[C];}});A="<"+A+">";}return $.element(this.createElement(A)).set(B);},newTextNode:function(A){return this.createTextNode(A); +},getDocument:function(){return this;},getWindow:function(){return this.defaultView||this.parentWindow;},purge:function(){var C=this.getElementsByTagName("*"); +for(var B=0,A=C.length;B1);A.each(function(E){var F=this.getElementsByTagName(E.trim());(B)?C.extend(F):C=F;},this);return new Elements(C,{ddup:B,cash:!D}); +}});Element.Storage={get:function(A){return(this[A]||(this[A]={}));}};Element.Inserters=new Hash({before:function(B,A){if(A.parentNode){A.parentNode.insertBefore(B,A); +}},after:function(B,A){if(!A.parentNode){return ;}var C=A.nextSibling;(C)?A.parentNode.insertBefore(B,C):A.parentNode.appendChild(B);},bottom:function(B,A){A.appendChild(B); +},top:function(B,A){var C=A.firstChild;(C)?A.insertBefore(B,C):A.appendChild(B);}});Element.Inserters.inside=Element.Inserters.bottom;Element.Inserters.each(function(C,B){var A=B.capitalize(); +Element.implement("inject"+A,function(D){C(this,$(D,true));return this;});Element.implement("grab"+A,function(D){C($(D,true),this);return this;});});Element.implement({getDocument:function(){return this.ownerDocument; +},getWindow:function(){return this.ownerDocument.getWindow();},getElementById:function(D,C){var B=this.ownerDocument.getElementById(D);if(!B){return null; +}for(var A=B.parentNode;A!=this;A=A.parentNode){if(!A){return null;}}return $.element(B,C);},set:function(D,B){switch($type(D)){case"object":for(var C in D){this.set(C,D[C]); +}break;case"string":var A=Element.Properties.get(D);(A&&A.set)?A.set.apply(this,Array.slice(arguments,1)):this.setProperty(D,B);}return this;},get:function(B){var A=Element.Properties.get(B); +return(A&&A.get)?A.get.apply(this,Array.slice(arguments,1)):this.getProperty(B);},erase:function(B){var A=Element.Properties.get(B);(A&&A.erase)?A.erase.apply(this,Array.slice(arguments,1)):this.removeProperty(B); +return this;},match:function(A){return(!A||Element.get(this,"tag")==A);},inject:function(B,A){Element.Inserters.get(A||"bottom")(this,$(B,true));return this; +},wraps:function(B,A){B=$(B,true);return this.replaces(B).grab(B,A);},grab:function(B,A){Element.Inserters.get(A||"bottom")($(B,true),this);return this; +},appendText:function(B,A){return this.grab(this.getDocument().newTextNode(B),A);},adopt:function(){Array.flatten(arguments).each(function(A){A=$(A,true); +if(A){this.appendChild(A);}},this);return this;},dispose:function(){return(this.parentNode)?this.parentNode.removeChild(this):this;},clone:function(D,C){switch($type(this)){case"element":var H={}; +for(var G=0,E=this.attributes.length;G1),cash:!G});}});Element.implement({match:function(B){if(!B){return true;}var D=Selectors.Utils.parseTagAndID(B); +var A=D[0],E=D[1];if(!Selectors.Filters.byID(this,E)||!Selectors.Filters.byTag(this,A)){return false;}var C=Selectors.Utils.parseSelector(B);return(C)?Selectors.Utils.filter(this,C,{}):true; +}});var Selectors={Cache:{nth:{},parsed:{}}};Selectors.RegExps={id:(/#([\w-]+)/),tag:(/^(\w+|\*)/),quick:(/^(\w+|\*)$/),splitter:(/\s*([+>~\s])\s*([a-zA-Z#.*:\[])/g),combined:(/\.([\w-]+)|\[(\w+)(?:([!*^$~|]?=)["']?(.*?)["']?)?\]|:([\w-]+)(?:\(["']?(.*?)?["']?\)|$)/g)}; +Selectors.Utils={chk:function(B,C){if(!C){return true;}var A=$uid(B);if(!C[A]){return C[A]=true;}return false;},parseNthArgument:function(F){if(Selectors.Cache.nth[F]){return Selectors.Cache.nth[F]; +}var C=F.match(/^([+-]?\d*)?([a-z]+)?([+-]?\d*)?$/);if(!C){return false;}var E=parseInt(C[1]);var B=(E||E===0)?E:1;var D=C[2]||false;var A=parseInt(C[3])||0; +if(B!=0){A--;while(A<1){A+=B;}while(A>=B){A-=B;}}else{B=A;D="index";}switch(D){case"n":C={a:B,b:A,special:"n"};break;case"odd":C={a:2,b:0,special:"n"}; +break;case"even":C={a:2,b:1,special:"n"};break;case"first":C={a:0,special:"index"};break;case"last":C={special:"last-child"};break;case"only":C={special:"only-child"}; +break;default:C={a:(B-1),special:"index"};}return Selectors.Cache.nth[F]=C;},parseSelector:function(E){if(Selectors.Cache.parsed[E]){return Selectors.Cache.parsed[E]; +}var D,H={classes:[],pseudos:[],attributes:[]};while((D=Selectors.RegExps.combined.exec(E))){var I=D[1],G=D[2],F=D[3],B=D[4],C=D[5],J=D[6];if(I){H.classes.push(I); +}else{if(C){var A=Selectors.Pseudo.get(C);if(A){H.pseudos.push({parser:A,argument:J});}else{H.attributes.push({name:C,operator:"=",value:J});}}else{if(G){H.attributes.push({name:G,operator:F,value:B}); +}}}}if(!H.classes.length){delete H.classes;}if(!H.attributes.length){delete H.attributes;}if(!H.pseudos.length){delete H.pseudos;}if(!H.classes&&!H.attributes&&!H.pseudos){H=null; +}return Selectors.Cache.parsed[E]=H;},parseTagAndID:function(B){var A=B.match(Selectors.RegExps.tag);var C=B.match(Selectors.RegExps.id);return[(A)?A[1]:"*",(C)?C[1]:false]; +},filter:function(F,C,E){var D;if(C.classes){for(D=C.classes.length;D--;D){var G=C.classes[D];if(!Selectors.Filters.byClass(F,G)){return false;}}}if(C.attributes){for(D=C.attributes.length; +D--;D){var B=C.attributes[D];if(!Selectors.Filters.byAttribute(F,B.name,B.operator,B.value)){return false;}}}if(C.pseudos){for(D=C.pseudos.length;D--;D){var A=C.pseudos[D]; +if(!Selectors.Filters.byPseudo(F,A.parser,A.argument,E)){return false;}}}return true;},getByTagAndID:function(B,A,D){if(D){var C=(B.getElementById)?B.getElementById(D,true):Element.getElementById(B,D,true); +return(C&&Selectors.Filters.byTag(C,A))?[C]:[];}else{return B.getElementsByTagName(A);}},search:function(J,I,O){var B=[];var C=I.trim().replace(Selectors.RegExps.splitter,function(Z,Y,X){B.push(Y); +return":)"+X;}).split(":)");var K,F,E,V;for(var U=0,Q=C.length;U":function(H,G,I,A,F){var C=Selectors.Utils.getByTagAndID(G,I,A);for(var E=0,D=C.length;EA){return false; +}}return(C==A);},even:function(B,A){return Selectors.Pseudo["nth-child"].call(this,"2n+1",A);},odd:function(B,A){return Selectors.Pseudo["nth-child"].call(this,"2n",A); +}});Element.Events.domready={onAdd:function(A){if(Browser.loaded){A.call(this);}}};(function(){var B=function(){if(Browser.loaded){return ;}Browser.loaded=true; +window.fireEvent("domready");document.fireEvent("domready");};switch(Browser.Engine.name){case"webkit":(function(){(["loaded","complete"].contains(document.readyState))?B():arguments.callee.delay(50); +})();break;case"trident":var A=document.createElement("div");(function(){($try(function(){A.doScroll("left");return $(A).inject(document.body).set("html","temp").dispose(); +}))?B():arguments.callee.delay(50);})();break;default:window.addEvent("load",B);document.addEvent("DOMContentLoaded",B);}})();var JSON=new Hash({encode:function(B){switch($type(B)){case"string":return'"'+B.replace(/[\x00-\x1f\\"]/g,JSON.$replaceChars)+'"'; +case"array":return"["+String(B.map(JSON.encode).filter($defined))+"]";case"object":case"hash":var A=[];Hash.each(B,function(E,D){var C=JSON.encode(E);if(C){A.push(JSON.encode(D)+":"+C); +}});return"{"+A+"}";case"number":case"boolean":return String(B);case false:return"null";}return null;},$specialChars:{"\b":"\\b","\t":"\\t","\n":"\\n","\f":"\\f","\r":"\\r",'"':'\\"',"\\":"\\\\"},$replaceChars:function(A){return JSON.$specialChars[A]||"\\u00"+Math.floor(A.charCodeAt()/16).toString(16)+(A.charCodeAt()%16).toString(16); +},decode:function(string,secure){if($type(string)!="string"||!string.length){return null;}if(secure&&!(/^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]*$/).test(string.replace(/\\./g,"@").replace(/"[^"\\\n\r]*"/g,""))){return null; +}return eval("("+string+")");}});Native.implement([Hash,Array,String,Number],{toJSON:function(){return JSON.encode(this);}});var Cookie=new Class({Implements:Options,options:{path:false,domain:false,duration:false,secure:false,document:document},initialize:function(B,A){this.key=B; +this.setOptions(A);},write:function(B){B=encodeURIComponent(B);if(this.options.domain){B+="; domain="+this.options.domain;}if(this.options.path){B+="; path="+this.options.path; +}if(this.options.duration){var A=new Date();A.setTime(A.getTime()+this.options.duration*24*60*60*1000);B+="; expires="+A.toGMTString();}if(this.options.secure){B+="; secure"; +}this.options.document.cookie=this.key+"="+B;return this;},read:function(){var A=this.options.document.cookie.match("(?:^|;)\\s*"+this.key.escapeRegExp()+"=([^;]*)"); +return(A)?decodeURIComponent(A[1]):null;},dispose:function(){new Cookie(this.key,$merge(this.options,{duration:-1})).write("");return this;}});Cookie.write=function(B,C,A){return new Cookie(B,A).write(C); +};Cookie.read=function(A){return new Cookie(A).read();};Cookie.dispose=function(B,A){return new Cookie(B,A).dispose();};var Swiff=new Class({Implements:[Options],options:{id:null,height:1,width:1,container:null,properties:{},params:{quality:"high",allowScriptAccess:"always",wMode:"transparent",swLiveConnect:true},callBacks:{},vars:{}},toElement:function(){return this.object; +},initialize:function(L,M){this.instance="Swiff_"+$time();this.setOptions(M);M=this.options;var B=this.id=M.id||this.instance;var A=$(M.container);Swiff.CallBacks[this.instance]={}; +var E=M.params,G=M.vars,F=M.callBacks;var H=$extend({height:M.height,width:M.width},M.properties);var K=this;for(var D in F){Swiff.CallBacks[this.instance][D]=(function(N){return function(){return N.apply(K.object,arguments); +};})(F[D]);G[D]="Swiff.CallBacks."+this.instance+"."+D;}E.flashVars=Hash.toQueryString(G);if(Browser.Engine.trident){H.classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"; +E.movie=L;}else{H.type="application/x-shockwave-flash";H.data=L;}var J=''; +}}J+="";this.object=((A)?A.empty():new Element("div")).set("html",J).firstChild;},replaces:function(A){A=$(A,true);A.parentNode.replaceChild(this.toElement(),A); +return this;},inject:function(A){$(A,true).appendChild(this.toElement());return this;},remote:function(){return Swiff.remote.apply(Swiff,[this.toElement()].extend(arguments)); +}});Swiff.CallBacks={};Swiff.remote=function(obj,fn){var rs=obj.CallFunction(''+__flash__argumentsToXML(arguments,2)+""); +return eval(rs);};var Fx=new Class({Implements:[Chain,Events,Options],options:{fps:50,unit:false,duration:500,link:"ignore",transition:function(A){return -(Math.cos(Math.PI*A)-1)/2; +}},initialize:function(A){this.subject=this.subject||this;this.setOptions(A);this.options.duration=Fx.Durations[this.options.duration]||this.options.duration.toInt(); +var B=this.options.wait;if(B===false){this.options.link="cancel";}},step:function(){var A=$time();if(A=(7-4*B)/11){C=-Math.pow((11-6*B-11*D)/4,2)+A*A; +break;}}return C;},Elastic:function(B,A){return Math.pow(2,10*--B)*Math.cos(20*B*Math.PI*(A[0]||1)/3);}});["Quad","Cubic","Quart","Quint"].each(function(B,A){Fx.Transitions[B]=new Fx.Transition(function(C){return Math.pow(C,[A+2]); +});});var Request=new Class({Implements:[Chain,Events,Options],options:{url:"",data:"",headers:{"X-Requested-With":"XMLHttpRequest",Accept:"text/javascript, text/html, application/xml, text/xml, */*"},async:true,format:false,method:"post",link:"ignore",isSuccess:null,emulation:true,urlEncoded:true,encoding:"utf-8",evalScripts:false,evalResponse:false},initialize:function(A){this.xhr=new Browser.Request(); +this.setOptions(A);this.options.isSuccess=this.options.isSuccess||this.isSuccess;this.headers=new Hash(this.options.headers);},onStateChange:function(){if(this.xhr.readyState!=4||!this.running){return ; +}this.running=false;this.status=0;$try(function(){this.status=this.xhr.status;}.bind(this));if(this.options.isSuccess.call(this,this.status)){this.response={text:this.xhr.responseText,xml:this.xhr.responseXML}; +this.success(this.response.text,this.response.xml);}else{this.response={text:null,xml:null};this.failure();}this.xhr.onreadystatechange=$empty;},isSuccess:function(){return((this.status>=200)&&(this.status<300)); +},processScripts:function(A){if(this.options.evalResponse||(/(ecma|java)script/).test(this.getHeader("Content-type"))){return $exec(A);}return A.stripScripts(this.options.evalScripts); +},success:function(B,A){this.onSuccess(this.processScripts(B),A);},onSuccess:function(){this.fireEvent("complete",arguments).fireEvent("success",arguments).callChain(); +},failure:function(){this.onFailure();},onFailure:function(){this.fireEvent("complete").fireEvent("failure",this.xhr);},setHeader:function(A,B){this.headers.set(A,B); +return this;},getHeader:function(A){return $try(function(){return this.xhr.getResponseHeader(A);}.bind(this));},check:function(A){if(!this.running){return true; +}switch(this.options.link){case"cancel":this.cancel();return true;case"chain":this.chain(A.bind(this,Array.slice(arguments,1)));return false;}return false; +},send:function(I){if(!this.check(arguments.callee,I)){return this;}this.running=true;var G=$type(I);if(G=="string"||G=="element"){I={data:I};}var D=this.options; +I=$extend({data:D.data,url:D.url,method:D.method},I);var E=I.data,B=I.url,A=I.method;switch($type(E)){case"element":E=$(E).toQueryString();break;case"object":case"hash":E=Hash.toQueryString(E); +}if(this.options.format){var H="format="+this.options.format;E=(E)?H+"&"+E:H;}if(this.options.emulation&&["put","delete"].contains(A)){var F="_method="+A; +E=(E)?F+"&"+E:F;A="post";}if(this.options.urlEncoded&&A=="post"){var C=(this.options.encoding)?"; charset="+this.options.encoding:"";this.headers.set("Content-type","application/x-www-form-urlencoded"+C); +}if(E&&A=="get"){B=B+(B.contains("?")?"&":"?")+E;E=null;}this.xhr.open(A.toUpperCase(),B,this.options.async);this.xhr.onreadystatechange=this.onStateChange.bind(this); +this.headers.each(function(K,J){if(!$try(function(){this.xhr.setRequestHeader(J,K);return true;}.bind(this))){this.fireEvent("exception",[J,K]);}},this); +this.fireEvent("request");this.xhr.send(E);if(!this.options.async){this.onStateChange();}return this;},cancel:function(){if(!this.running){return this; +}this.running=false;this.xhr.abort();this.xhr.onreadystatechange=$empty;this.xhr=new Browser.Request();this.fireEvent("cancel");return this;}});(function(){var A={}; +["get","post","put","delete","GET","POST","PUT","DELETE"].each(function(B){A[B]=function(){var C=Array.link(arguments,{url:String.type,data:$defined}); +return this.send($extend(C,{method:B.toLowerCase()}));};});Request.implement(A);})();Element.Properties.send={set:function(A){var B=this.retrieve("send"); +if(B){B.cancel();}return this.eliminate("send").store("send:options",$extend({data:this,link:"cancel",method:this.get("method")||"post",url:this.get("action")},A)); +},get:function(A){if(A||!this.retrieve("send")){if(A||!this.retrieve("send:options")){this.set("send",A);}this.store("send",new Request(this.retrieve("send:options"))); +}return this.retrieve("send");}};Element.implement({send:function(A){var B=this.get("send");B.send({data:this,url:A||B.options.url});return this;}});Request.HTML=new Class({Extends:Request,options:{update:false,evalScripts:true,filter:false},processHTML:function(C){var B=C.match(/]*>([\s\S]*?)<\/body>/i); +C=(B)?B[1]:C;var A=new Element("div");return $try(function(){var D=""+C+"",G;if(Browser.Engine.trident){G=new ActiveXObject("Microsoft.XMLDOM"); +G.async=false;G.loadXML(D);}else{G=new DOMParser().parseFromString(D,"text/xml");}D=G.getElementsByTagName("root")[0];for(var F=0,E=D.childNodes.length; +F2QB5_FA9EUI)l>gYo+JHGN`; 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 * { + background-color: #e5bf5e; + border: solid 2px; + padding: 1em 1em 1em 1em; + margin: 1em; + text-align: left; +} + +textarea, select, input { + background-color: #ffec9e; + border: 1px solid; + padding: 1px 3px 1px 3px; +} + +table.user input[type="text"], table.user input[type="password"] { + width: 15em; +} + +table.peer textarea { + height: 20em; + width: 42.3em; + +} + +table.peer input[type="text"] { + width: 38em; +} + +.menu { + text-align: right; + background-color: #e5bf5e; + padding: 3px; + border-bottom: solid 2px; +} + +a { + color: black; + text-decoration: none; +} + +a:hover { + text-decoration: underline; +} + +h1 { + margin-top: 1.5em; + font-size: 2.1em; + white-space: nowrap; +} + +hr { + border: solid 1px; +} + +a img { + border: none; +} + +.center { + text-align: center; +} + +.left { + text-align: left; +} + +.right { + text-align: right; +} + +.fleft { + margin-right: 2em; + float: left; +} + +.fright { + float: left; +} + +.cleft { + clear:left; +} + +.cright { + clear:right; +} + +.both { + clear:both; +} + +.error { + color: #dd0000; +} + +.even { + cursor : pointer; +} + +.even a, .odd a { + text-decoration: none; +} + +.odd { + background-color: #f2cd6f; + cursor : pointer; +} + +.head { + background-color: #ffec9e; +} + +table.list * { + padding: 0px 1em 0px 0.2em; +} + +table.list tr td, table.list tr th { + border: solid 1px; + border-color: black; +} + diff --git a/src/medsrv/templates/user/add.cs b/src/medsrv/templates/user/add.cs new file mode 100755 index 000000000..8ba4e5c96 --- /dev/null +++ b/src/medsrv/templates/user/add.cs @@ -0,0 +1,28 @@ + +
+ +
+ + + + + + + + + + + + + + + + + + +
min. characters
+ + +
+
+ diff --git a/src/medsrv/templates/user/edit.cs b/src/medsrv/templates/user/edit.cs new file mode 100755 index 000000000..1f168498b --- /dev/null +++ b/src/medsrv/templates/user/edit.cs @@ -0,0 +1,35 @@ + +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + +
min. characters
+ + + +
+
+ diff --git a/src/medsrv/templates/user/help.cs b/src/medsrv/templates/user/help.cs new file mode 100644 index 000000000..58615c14a --- /dev/null +++ b/src/medsrv/templates/user/help.cs @@ -0,0 +1,34 @@ + +
+

strongSwan Mediation Service web frontend

+

This web application builds the end user front end for a Mediation Service +as defined in the + +IKEv2 Mediation Extension draft.

+

Mediation connection

+

The authentication between Mediation Server and connecting clients is based +on RSA public keys. The identities used for IKEv2 are the public key identifier +of each clients key, encapsulated in a ID_KEY_ID identity.

+

The public key of this Mediation Server is:

+
-----BEGIN PUBLIC KEY-----
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzZRsIp99UrIdoctThOfc
+r2Up92BTSlY1Xv1J6Hqcbx3dX/MDvX60nCPeA63Eh0VvQetfkpR73I/42+RD+NES
+4NosmBRefE0c0Vzd0IV39NTz0KLh2jwIyUzYGXWHUZMeepckzEPXOhG44XaiaLTN
+u/OZXLCXI6vJv8R3wl5xSkZhqEwHi+dATYmGvlXyBDfjprJ4o8yJrsCFlB8aGq+v
+SyKuFG/kaE1VZ9wwZYoyCH0BuYUVBwyxZTMRy2EC+CqDxjjCp5mF27lgB1Lpy8Jy
+AUpcVHtKtZEww6lIZYv/eUtvICz5WTn/pzsQUh8FwGDOyxX4WX7ZXXK55AXuMfG1
+2QIDAQAB
+-----END PUBLIC KEY-----
+

The Mediation Server is reachable at mediation.strongswan.org.

+The mediation server allows connections from all registered peers.

+

Mediated connections

+

The authentication between mediated clients is done between clients, they +can use own keys or the same keys as defined for authentication of the +mediation connection. +

+
+ +
+
+
+ diff --git a/src/medsrv/templates/user/login.cs b/src/medsrv/templates/user/login.cs new file mode 100755 index 000000000..1d6eadbbc --- /dev/null +++ b/src/medsrv/templates/user/login.cs @@ -0,0 +1,23 @@ + +
+ +
+ + + + + + + + + + + + + +
+ + +
+
+ diff --git a/src/medsrv/user.c b/src/medsrv/user.c new file mode 100644 index 000000000..032859e2e --- /dev/null +++ b/src/medsrv/user.c @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2008 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 . + * + * 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. + * + * $Id$ + */ + +#include "user.h" + +typedef struct private_user_t private_user_t; + +/** + * private data of user + */ +struct private_user_t { + + /** + * public functions + */ + user_t public; + + /** + * user id, if we are logged in; otherwise 0 + */ + u_int user; +}; + +/** + * Implementation of user_t.set_user + */ +static void set_user(private_user_t *this, u_int id) +{ + this->user = id; +} + +/** + * Implementation of user_t.get_user + */ +static u_int get_user(private_user_t *this) +{ + return this->user; +} + +/** + * Implementation of context_t.destroy + */ +static void destroy(private_user_t *this) +{ + free(this); +} + +/* + * see header file + */ +user_t *user_create(void *param) +{ + private_user_t *this= malloc_thing(private_user_t); + + this->public.set_user = (void(*)(user_t*,u_int id))set_user; + this->public.get_user = (u_int(*)(user_t*))get_user; + this->public.context.destroy = (void(*)(context_t*))destroy; + + this->user = 0; + + return &this->public; +} + diff --git a/src/medsrv/user.h b/src/medsrv/user.h new file mode 100644 index 000000000..b411f7c6f --- /dev/null +++ b/src/medsrv/user.h @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2008 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 . + * + * 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. + * + * $Id$ + */ + +#ifndef USER_H_ +#define USER_H_ + +#include +#include + +typedef struct user_t user_t; + +/** + * Per session context. Contains user user state and data. + */ +struct user_t { + + /** + * implements context_t interface + */ + context_t context; + + /** + * Set the user ID of the logged in user. + */ + void (*set_user)(user_t *this, u_int id); + + /** + * Get the user ID of the logged in user. + */ + u_int (*get_user)(user_t *this); +}; + +/** + * Create a user instance. + */ +user_t *user_create(void *param); + +#endif /* USER_H_ @} */