Skip to content
Snippets Groups Projects
Commit 6dd93287 authored by gauthier's avatar gauthier
Browse files

commit my code before applying sebastian Held patches 13

git-svn-id: http://svn.eurecom.fr/openair4G/trunk@7045 818b1a75-f10b-46b9-bf7c-635c3b92a50f
parent 202f8cd0
No related branches found
No related tags found
No related merge requests found
Showing
with 108 additions and 28 deletions
...@@ -46,6 +46,7 @@ ...@@ -46,6 +46,7 @@
#include "NwGtpv1uLog.h" #include "NwGtpv1uLog.h"
#include "assertions.h" #include "assertions.h"
#include "intertask_interface.h"
#include "gtpv1u.h" #include "gtpv1u.h"
#if defined(ENB_MODE) #if defined(ENB_MODE)
...@@ -576,6 +577,7 @@ nwGtpv1uHandleEchoReq(NW_IN NwGtpv1uStackT *thiz, ...@@ -576,6 +577,7 @@ nwGtpv1uHandleEchoReq(NW_IN NwGtpv1uStackT *thiz,
NwGtpv1uRcT rc = NW_GTPV1U_FAILURE; NwGtpv1uRcT rc = NW_GTPV1U_FAILURE;
NwU16T seqNum = 0; NwU16T seqNum = 0;
NwGtpv1uMsgHandleT hMsg = 0; NwGtpv1uMsgHandleT hMsg = 0;
int bufLen = 0;
seqNum = ntohs(*(NwU16T *) (msgBuf + (((*msgBuf) & 0x02) ? 8 : 4))); seqNum = ntohs(*(NwU16T *) (msgBuf + (((*msgBuf) & 0x02) ? 8 : 4)));
...@@ -592,6 +594,13 @@ nwGtpv1uHandleEchoReq(NW_IN NwGtpv1uStackT *thiz, ...@@ -592,6 +594,13 @@ nwGtpv1uHandleEchoReq(NW_IN NwGtpv1uStackT *thiz,
0, 0,
(&hMsg)); (&hMsg));
bufLen = sizeof(NwGtpv1uIeTv1T)+ ((NwGtpv1uMsgT*)hMsg)->msgLen;
((NwGtpv1uMsgT*)hMsg)->msgBuf = itti_malloc(
TASK_GTPV1_U,
TASK_UDP,
bufLen);
((NwGtpv1uMsgT*)hMsg)->msgBufLen = bufLen;
NW_ASSERT(NW_GTPV1U_OK == rc); NW_ASSERT(NW_GTPV1U_OK == rc);
/* /*
......
...@@ -104,6 +104,9 @@ nwGtpv1uMsgNew( NW_IN NwGtpv1uStackHandleT hGtpuStackHandle, ...@@ -104,6 +104,9 @@ nwGtpv1uMsgNew( NW_IN NwGtpv1uStackHandleT hGtpuStackHandle,
4 : 0); 4 : 0);
//NW_GTPV1U_EPC_SPECIFIC_HEADER_SIZE : (NW_GTPV1U_EPC_SPECIFIC_HEADER_SIZE - 4)); //NW_GTPV1U_EPC_SPECIFIC_HEADER_SIZE : (NW_GTPV1U_EPC_SPECIFIC_HEADER_SIZE - 4));
pMsg->msgBuf = NULL;
pMsg->msgBufLen = 0;
pMsg->msgBufOffset = 0;
*phMsg = (NwGtpv1uMsgHandleT) pMsg; *phMsg = (NwGtpv1uMsgHandleT) pMsg;
return NW_GTPV1U_OK; return NW_GTPV1U_OK;
} }
......
...@@ -92,11 +92,9 @@ void generate_autn(u8 sqn[6], u8 ak[6], u8 amf[2], u8 mac_a[8], u8 autn[16]); ...@@ -92,11 +92,9 @@ void generate_autn(u8 sqn[6], u8 ak[6], u8 amf[2], u8 mac_a[8], u8 autn[16]);
int generate_vector(uint64_t imsi, uint8_t key[16], uint8_t plmn[3], int generate_vector(uint64_t imsi, uint8_t key[16], uint8_t plmn[3],
uint8_t sqn[6], auc_vector_t *vector); uint8_t sqn[6], auc_vector_t *vector);
inline
void kdf(uint8_t *key, uint16_t key_len, uint8_t *s, uint16_t s_len, uint8_t *out, void kdf(uint8_t *key, uint16_t key_len, uint8_t *s, uint16_t s_len, uint8_t *out,
uint16_t out_len); uint16_t out_len);
inline
void derive_kasme(uint8_t ck[16], uint8_t ik[16], uint8_t plmn[3], uint8_t sqn[6], void derive_kasme(uint8_t ck[16], uint8_t ik[16], uint8_t plmn[3], uint8_t sqn[6],
uint8_t ak[6], uint8_t kasme[32]); uint8_t ak[6], uint8_t kasme[32]);
......
...@@ -140,7 +140,6 @@ int generate_vector(uint64_t imsi, uint8_t key[16], uint8_t plmn[3], ...@@ -140,7 +140,6 @@ int generate_vector(uint64_t imsi, uint8_t key[16], uint8_t plmn[3],
uint8_t ck[16]; uint8_t ck[16];
uint8_t ik[16]; uint8_t ik[16];
uint8_t ak[6]; uint8_t ak[6];
int i;
if (vector == NULL) { if (vector == NULL) {
return EINVAL; return EINVAL;
......
...@@ -69,7 +69,7 @@ void generate_random(uint8_t *random_p, ssize_t length) ...@@ -69,7 +69,7 @@ void generate_random(uint8_t *random_p, ssize_t length)
// pthread_mutex_unlock(&random_state.lock); // pthread_mutex_unlock(&random_state.lock);
// mpz_export(random_p, NULL, 1, length, 0, 0, random_nb); // mpz_export(random_p, NULL, 1, length, 0, 0, random_nb);
int r = 0, i, mask = 0, shift; int i;//r = 0, mask = 0, shift;
for (i = 0; i < length; i ++) { for (i = 0; i < length; i ++) {
// if ((i % sizeof(i)) == 0) // if ((i % sizeof(i)) == 0)
......
## MySQL mandatory options ## MySQL mandatory options
MYSQL_server = "127.0.0.1"; MYSQL_server = "@MYSQL_server@";
MYSQL_user = "hssadmin"; MYSQL_user = "@MYSQL_user@";
MYSQL_pass = "admin"; MYSQL_pass = "@MYSQL_pass@";
MYSQL_db = "oai_db"; MYSQL_db = "@MYSQL_db@";
## HSS options
OPERATOR_key = "@OPERATOR_key@";
## Freediameter options ## Freediameter options
FD_conf = "@AM_CONF_DIR@/hss_fd.conf"; FD_conf = "@FREEDIAMETER_PATH@/../etc/freeDiameter/hss_fd.conf";
...@@ -51,7 +51,7 @@ echo "Creating HSS certificate for user '$HOSTNAME'.'$REALM'" ...@@ -51,7 +51,7 @@ echo "Creating HSS certificate for user '$HOSTNAME'.'$REALM'"
# openssl ca -cert cacert.pem -keyfile cakey.pem -in hss.csr.pem -out hss.cert.pem -outdir . -batch # openssl ca -cert cacert.pem -keyfile cakey.pem -in hss.csr.pem -out hss.cert.pem -outdir . -batch
# Create a Root Certification Authority Certificate # Create a Root Certification Authority Certificate
openssl req -new -batch -x509 -days 3650 -nodes -newkey rsa:1024 -out cacert.pem -keyout cakey.pem -subj /CN=$REALM/C=FR/ST=PACA/L=Aix/O=Eurecom/OU=CM openssl req -new -batch -x509 -days 3650 -nodes -newkey rsa:1024 -out hss.cacert.pem -keyout hss.cakey.pem -subj /CN=$REALM/C=FR/ST=PACA/L=Aix/O=Eurecom/OU=CM
# Generate a Private Key # Generate a Private Key
openssl genrsa -out hss.key.pem 1024 openssl genrsa -out hss.key.pem 1024
...@@ -60,7 +60,7 @@ openssl genrsa -out hss.key.pem 1024 ...@@ -60,7 +60,7 @@ openssl genrsa -out hss.key.pem 1024
openssl req -new -batch -out hss.csr.pem -key hss.key.pem -subj /CN=$HOSTNAME.$REALM/C=FR/ST=PACA/L=Aix/O=Eurecom/OU=CM openssl req -new -batch -out hss.csr.pem -key hss.key.pem -subj /CN=$HOSTNAME.$REALM/C=FR/ST=PACA/L=Aix/O=Eurecom/OU=CM
# Certification authority # Certification authority
openssl ca -cert cacert.pem -keyfile cakey.pem -in hss.csr.pem -out hss.cert.pem -outdir . -batch openssl ca -cert hss.cacert.pem -keyfile hss.cakey.pem -in hss.csr.pem -out hss.cert.pem -outdir . -batch
if [ ! -d /usr/local/etc/freeDiameter ] if [ ! -d /usr/local/etc/freeDiameter ]
then then
...@@ -68,7 +68,7 @@ then ...@@ -68,7 +68,7 @@ then
sudo mkdir /usr/local/etc/freeDiameter/ sudo mkdir /usr/local/etc/freeDiameter/
fi fi
sudo cp -upv cakey.pem hss.cert.pem cacert.pem hss.key.pem /usr/local/etc/freeDiameter/ sudo cp -upv hss.cakey.pem hss.cert.pem hss.cacert.pem hss.key.pem /usr/local/etc/freeDiameter/
# openssl genrsa -out $hss.key.pem 1024 # openssl genrsa -out $hss.key.pem 1024
# openssl req -new -batch -out $hss.csr.pem -key $hss.key.pem -subj /CN=$hss.test.fr/C=FR/ST=Biot/L=Aix/O=test.fr/OU=mobiles # openssl req -new -batch -out $hss.csr.pem -key $hss.key.pem -subj /CN=$hss.test.fr/C=FR/ST=Biot/L=Aix/O=test.fr/OU=mobiles
......
...@@ -377,7 +377,6 @@ int hss_mysql_push_rand_sqn(const char *imsi, uint8_t *rand_p, uint8_t *sqn) ...@@ -377,7 +377,6 @@ int hss_mysql_push_rand_sqn(const char *imsi, uint8_t *rand_p, uint8_t *sqn)
{ {
int status = 0, i; int status = 0, i;
MYSQL_RES *res; MYSQL_RES *res;
MYSQL_ROW row;
char query[255]; char query[255];
int query_length = 0; int query_length = 0;
uint64_t sqn_decimal = 0; uint64_t sqn_decimal = 0;
......
...@@ -29,6 +29,7 @@ ...@@ -29,6 +29,7 @@
#include <stdio.h> #include <stdio.h>
#include <stdint.h> #include <stdint.h>
#include <pthread.h>
#include <mysql/mysql.h> #include <mysql/mysql.h>
#include <netinet/in.h> /* To provide internet addresses strings helpers */ #include <netinet/in.h> /* To provide internet addresses strings helpers */
......
...@@ -34,9 +34,12 @@ ...@@ -34,9 +34,12 @@
* \version 0.1 * \version 0.1
*/ */
#include <freeDiameter/freeDiameter-host.h>
#include <freeDiameter/libfdproto.h>
#include <stdint.h> #include <stdint.h>
#include <string.h> #include <string.h>
#include <inttypes.h> #include <inttypes.h>
#include <pthread.h>
#include "hss_config.h" #include "hss_config.h"
#include "db_proto.h" #include "db_proto.h"
......
...@@ -31,7 +31,7 @@ ...@@ -31,7 +31,7 @@
#include "db_proto.h" #include "db_proto.h"
#include "s6a_proto.h" #include "s6a_proto.h"
inline char *experimental_retcode_2_string(int ret_code) char *experimental_retcode_2_string(int ret_code)
{ {
switch(ret_code) { switch(ret_code) {
/* Experimental-Result-Codes */ /* Experimental-Result-Codes */
...@@ -55,7 +55,7 @@ inline char *experimental_retcode_2_string(int ret_code) ...@@ -55,7 +55,7 @@ inline char *experimental_retcode_2_string(int ret_code)
return "DIAMETER_AVP_UNSUPPORTED"; return "DIAMETER_AVP_UNSUPPORTED";
} }
inline char *retcode_2_string(int ret_code) char *retcode_2_string(int ret_code)
{ {
switch(ret_code) { switch(ret_code) {
case ER_DIAMETER_SUCCESS: case ER_DIAMETER_SUCCESS:
......
...@@ -51,7 +51,7 @@ static struct session_handler *s6a_reg = NULL; ...@@ -51,7 +51,7 @@ static struct session_handler *s6a_reg = NULL;
static struct disp_hdl *handle; static struct disp_hdl *handle;
s6a_cnf_t s6a_cnf; s6a_cnf_t s6a_cnf;
#if !defined(FREEDIAMETER_VERSION_1_2_0) #if FREEDIAMETER_VERSION < 120
void s6a_cli_sess_cleanup(void * arg, char * sid, void * opaque); void s6a_cli_sess_cleanup(void * arg, char * sid, void * opaque);
void s6a_cli_sess_cleanup(void * arg, char * sid, void * opaque) void s6a_cli_sess_cleanup(void * arg, char * sid, void * opaque)
...@@ -311,7 +311,7 @@ int s6a_init(hss_config_t *hss_config_p) ...@@ -311,7 +311,7 @@ int s6a_init(hss_config_t *hss_config_p)
} }
/* Create handler for sessions */ /* Create handler for sessions */
#if !defined(FREEDIAMETER_VERSION_1_2_0) #if FREEDIAMETER_VERSION < 120
CHECK_FCT(fd_sess_handler_create(&s6a_reg, s6a_cli_sess_cleanup, NULL)); CHECK_FCT(fd_sess_handler_create(&s6a_reg, s6a_cli_sess_cleanup, NULL));
#else #else
session_state_dump dumper; session_state_dump dumper;
......
...@@ -232,7 +232,7 @@ int s6a_add_result_code(struct msg *ans, struct avp *failed_avp, ...@@ -232,7 +232,7 @@ int s6a_add_result_code(struct msg *ans, struct avp *failed_avp,
int s6a_add_ipv4_address(struct avp *avp, const char *ipv4_addr); int s6a_add_ipv4_address(struct avp *avp, const char *ipv4_addr);
int s6a_add_ipv6_address(struct avp *avp, const char *ipv6_addr); int s6a_add_ipv6_address(struct avp *avp, const char *ipv6_addr);
inline char *experimental_retcode_2_string(int ret_code); char *experimental_retcode_2_string(int ret_code);
inline char *retcode_2_string(int ret_code); char *retcode_2_string(int ret_code);
#endif /* S6A_PROTO_H_ */ #endif /* S6A_PROTO_H_ */
...@@ -34,6 +34,9 @@ ...@@ -34,6 +34,9 @@
* \version 0.1 * \version 0.1
*/ */
#include <freeDiameter/freeDiameter-host.h>
#include <freeDiameter/libfdproto.h>
#include "hss_config.h" #include "hss_config.h"
#include "db_proto.h" #include "db_proto.h"
#include "s6a_proto.h" #include "s6a_proto.h"
......
...@@ -34,6 +34,8 @@ ...@@ -34,6 +34,8 @@
* \version 0.1 * \version 0.1
*/ */
#include <freeDiameter/freeDiameter-host.h>
#include <freeDiameter/libfdproto.h>
#include "hss_config.h" #include "hss_config.h"
#include "db_proto.h" #include "db_proto.h"
#include "s6a_proto.h" #include "s6a_proto.h"
......
...@@ -42,15 +42,30 @@ ...@@ -42,15 +42,30 @@
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <unistd.h> #include <unistd.h>
#include <stdint.h>
#include <errno.h> #include <errno.h>
#include <getopt.h> #include <getopt.h>
#include "hss_config.h" #include "hss_config.h"
#include "hss_parser.h" #include "hss_parser.h"
#ifndef PACKAGE_NAME
#define PACKAGE_NAME "OPENAIR-HSS"
#endif
#ifndef PACKAGE_VERSION
#define PACKAGE_VERSION "UNKNOWN-EXPERIMENTAL"
#endif
#ifndef PACKAGE_BUGREPORT
#define PACKAGE_BUGREPORT "openair4G-devel@eurecom.fr"
#endif
// LG TODO fd_g_debug_lvl
int fd_g_debug_lvl = 1;
/* YACC forward declarations */ /* YACC forward declarations */
extern int yyparse (struct hss_config_s *hss_config_p); extern int yyparse (struct hss_config_s *hss_config_p);
extern uint8_t OP[16];
static int config_parse_command_line(int argc, char *argv[], static int config_parse_command_line(int argc, char *argv[],
hss_config_t *hss_config_p); hss_config_t *hss_config_p);
static int config_parse_file(hss_config_t *hss_config_p); static int config_parse_file(hss_config_t *hss_config_p);
...@@ -82,7 +97,37 @@ int config_init(int argc, char *argv[], hss_config_t *hss_config_p) ...@@ -82,7 +97,37 @@ int config_init(int argc, char *argv[], hss_config_t *hss_config_p)
abort(); abort();
} }
config_display(hss_config_p); config_display(hss_config_p);
return ret;
// post processing for op key
if (hss_config_p->operator_key) {
if (strlen(hss_config_p->operator_key) == 32) {
ret = sscanf(hss_config_p->operator_key,
"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
(unsigned int*)&OP[0],(unsigned int*)&OP[1],
(unsigned int*)&OP[2],(unsigned int*)&OP[3],
(unsigned int*)&OP[4],(unsigned int*)&OP[5],
(unsigned int*)&OP[6],(unsigned int*)&OP[7],
(unsigned int*)&OP[8],(unsigned int*)&OP[9],
(unsigned int*)&OP[10],(unsigned int*)&OP[11],
(unsigned int*)&OP[12],(unsigned int*)&OP[13],
(unsigned int*)&OP[14],(unsigned int*)&OP[15]);
if (ret != 16) {
fprintf(stderr,
"Error in configuration file: operator key: %s\n",
hss_config_p->operator_key);
abort();
}
} else {
fprintf(stderr,
"Error in configuration file: operator key length != 32 (16 hex bytes): %s\n",
hss_config_p->operator_key);
abort();
}
} else {
fprintf(stderr, "Error in configuration file: operator key is null\n");
abort();
}
return 0;
} }
static void display_banner(void) static void display_banner(void)
...@@ -119,6 +164,9 @@ static void config_display(hss_config_t *hss_config_p) ...@@ -119,6 +164,9 @@ static void config_display(hss_config_t *hss_config_p)
fprintf(stdout, "* FreeDiameter:\n"); fprintf(stdout, "* FreeDiameter:\n");
fprintf(stdout, "\t- Conf file ........: %s\n", fprintf(stdout, "\t- Conf file ........: %s\n",
hss_config_p->freediameter_config); hss_config_p->freediameter_config);
fprintf(stdout, "* Security:\n");
fprintf(stdout, "\t- Operator key......: %s\n",
hss_config_p->operator_key);
} }
static int config_parse_command_line(int argc, char *argv[], static int config_parse_command_line(int argc, char *argv[],
......
...@@ -35,6 +35,9 @@ typedef struct hss_config_s { ...@@ -35,6 +35,9 @@ typedef struct hss_config_s {
char *mysql_password; char *mysql_password;
char *mysql_database; char *mysql_database;
char *operator_key;
/* The freediameter configuration file */ /* The freediameter configuration file */
char *freediameter_config; char *freediameter_config;
......
...@@ -69,6 +69,7 @@ int fddlex(YYSTYPE *lvalp, YYLTYPE *llocp); ...@@ -69,6 +69,7 @@ int fddlex(YYSTYPE *lvalp, YYLTYPE *llocp);
%token MYSQL_SERVER %token MYSQL_SERVER
%token MYSQL_PASS %token MYSQL_PASS
%token MYSQL_DB %token MYSQL_DB
%token OPERATOR_KEY
%% %%
conffile: /* Empty is OK -- for simplicity here, we reject in daemon later */ conffile: /* Empty is OK -- for simplicity here, we reject in daemon later */
...@@ -76,6 +77,7 @@ conffile: /* Empty is OK -- for simplicity here, we reject in daemon later ...@@ -76,6 +77,7 @@ conffile: /* Empty is OK -- for simplicity here, we reject in daemon later
| conffile mysql_server | conffile mysql_server
| conffile mysql_user | conffile mysql_user
| conffile mysql_pass | conffile mysql_pass
| conffile operator_key
| conffile fdconf | conffile fdconf
| conffile errors | conffile errors
{ {
...@@ -108,6 +110,12 @@ mysql_server: MYSQL_SERVER '=' QSTRING ';' ...@@ -108,6 +110,12 @@ mysql_server: MYSQL_SERVER '=' QSTRING ';'
} }
; ;
operator_key: OPERATOR_KEY '=' QSTRING ';'
{
hss_config_p->operator_key = $3;
}
;
fdconf: FDCONF '=' QSTRING ';' fdconf: FDCONF '=' QSTRING ';'
{ {
hss_config_p->freediameter_config = $3; hss_config_p->freediameter_config = $3;
......
...@@ -113,6 +113,7 @@ qstring \"[^\"\n]*\" ...@@ -113,6 +113,7 @@ qstring \"[^\"\n]*\"
(?i:"MYSQL_user") { return MYSQL_USER; } (?i:"MYSQL_user") { return MYSQL_USER; }
(?i:"MYSQL_pass") { return MYSQL_PASS; } (?i:"MYSQL_pass") { return MYSQL_PASS; }
(?i:"MYSQL_db") { return MYSQL_DB; } (?i:"MYSQL_db") { return MYSQL_DB; }
(?i:"OPERATOR_key") { return OPERATOR_KEY; }
/* Valid single characters for yyparse */ /* Valid single characters for yyparse */
<*>[=,:;{}] { return yytext[0]; } <*>[=,:;{}] { return yytext[0]; }
......
...@@ -45,11 +45,11 @@ HOSTNAME=$(hostname -f) ...@@ -45,11 +45,11 @@ HOSTNAME=$(hostname -f)
echo "Creating MME certificate for user '$HOSTNAME'.'$REALM'" echo "Creating MME certificate for user '$HOSTNAME'.'$REALM'"
# CA self certificate # CA self certificate
openssl req -new -batch -x509 -days 3650 -nodes -newkey rsa:1024 -out cacert.pem -keyout cakey.pem -subj /CN=$REALM/C=FR/ST=PACA/L=Aix/O=Eurecom/OU=CM openssl req -new -batch -x509 -days 3650 -nodes -newkey rsa:1024 -out mme.cacert.pem -keyout mme.cakey.pem -subj /CN=$REALM/C=FR/ST=PACA/L=Aix/O=Eurecom/OU=CM
openssl genrsa -out user.key.pem 1024 openssl genrsa -out mme.key.pem 1024
openssl req -new -batch -out user.csr.pem -key user.key.pem -subj /CN=$HOSTNAME.$REALM/C=FR/ST=PACA/L=Aix/O=Eurecom/OU=CM openssl req -new -batch -out mme.csr.pem -key mme.key.pem -subj /CN=$HOSTNAME.$REALM/C=FR/ST=PACA/L=Aix/O=Eurecom/OU=CM
openssl ca -cert cacert.pem -keyfile cakey.pem -in user.csr.pem -out user.cert.pem -outdir . -batch openssl ca -cert mme.cacert.pem -keyfile mme.cakey.pem -in mme.csr.pem -out mme.cert.pem -outdir . -batch
if [ ! -d /usr/local/etc/freeDiameter ] if [ ! -d /usr/local/etc/freeDiameter ]
then then
...@@ -57,7 +57,7 @@ then ...@@ -57,7 +57,7 @@ then
sudo mkdir /usr/local/etc/freeDiameter/ sudo mkdir /usr/local/etc/freeDiameter/
fi fi
sudo cp -uv user.key.pem user.cert.pem cacert.pem cakey.pem /usr/local/etc/freeDiameter/ sudo cp -uv mme.key.pem mme.cert.pem mme.cacert.pem mme.cakey.pem /usr/local/etc/freeDiameter/
# openssl genrsa -out ubuntu.key.pem 1024 # openssl genrsa -out ubuntu.key.pem 1024
# openssl req -new -batch -x509 -out ubuntu.csr.pem -key ubuntu.key.pem -subj /CN=ubuntu.localdomain/C=FR/ST=BdR/L=Aix/O=fD/OU=Tests # openssl req -new -batch -x509 -out ubuntu.csr.pem -key ubuntu.key.pem -subj /CN=ubuntu.localdomain/C=FR/ST=BdR/L=Aix/O=fD/OU=Tests
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment