diff --git a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Build.pm b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Build.pm index 1cb6cc6a994805678c03615e6ae05ed20e0aeb57..bfe457b68cf61ce18aa8e239d5f3e353f42e2098 100644 --- a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Build.pm +++ b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Build.pm @@ -9,6 +9,7 @@ use Lemonldap::NG::Manager::Build::CTrees; use Lemonldap::NG::Manager::Build::PortalConstants; use Lemonldap::NG::Manager::Conf::Zero; use Data::Dumper; +use Regexp::Common 'URI'; use Regexp::Assemble; use JSON; use Getopt::Std; @@ -466,6 +467,7 @@ sub buildPortalConstants() { printf STDERR $format, $self->portalConstantsFile; open( F, '>', $self->portalConstantsFile ) or die($!); + my $urire = $RE{URI}{HTTP}{ -scheme=>qr/https?/ }{-keep}; my $content = < 'Lemonldap::NG::Handler::PSGI::Main'; +use constant URIRE => qr{$urire}; use constant { EOF for my $pe ( @@ -499,7 +502,7 @@ $portalConstsStr } # EXPORTER PARAMETERS -our \@EXPORT_OK = ( 'portalConsts', 'HANDLER', $exports ); +our \@EXPORT_OK = ( 'portalConsts', 'HANDLER', 'URIRE', $exports ); our %EXPORT_TAGS = ( 'all' => [ \@EXPORT_OK, 'import' ], ); our \@EXPORT = qw(import PE_OK); diff --git a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Issuer/CAS.pm b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Issuer/CAS.pm index 94891ad06c96fb84084cb0f7ffa5cc9c30b93310..781c87f15419029eec548d2ff6d4bb6b377b5796 100644 --- a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Issuer/CAS.pm +++ b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Issuer/CAS.pm @@ -12,9 +12,10 @@ use Lemonldap::NG::Portal::Main::Constants qw( PE_OK PE_BADURL PE_SENDRESPONSE + URIRE ); -our $VERSION = '2.0.9'; +our $VERSION = '2.0.12'; extends 'Lemonldap::NG::Portal::Main::Issuer', 'Lemonldap::NG::Portal::Lib::CAS'; @@ -93,8 +94,7 @@ sub storeEnvAndCheckGateway { return PE_SENDRESPONSE; } - if ( $service and $service =~ m#^(https?://[^/]+)(/.*)?$# ) { - my ( $host, $uri ) = ( $1, $2 ); + if ( $service and $service =~ URIRE ) { my $app = $self->getCasApp($service); if ($app) { diff --git a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Issuer/Get.pm b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Issuer/Get.pm index 4f56c403c68027d52702cfc87af55a1536acf7e2..347ed44c9291e71777b2877b7f63d6c254f3efbb 100644 --- a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Issuer/Get.pm +++ b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Issuer/Get.pm @@ -5,9 +5,9 @@ use Mouse; use URI::Escape; use Lemonldap::NG::Common::FormEncode; use Lemonldap::NG::Portal::Main::Constants - qw(PE_OK PE_BADURL PE_GET_SERVICE_NOT_ALLOWED); + qw(PE_OK PE_BADURL PE_GET_SERVICE_NOT_ALLOWED URIRE); -our $VERSION = '2.0.9'; +our $VERSION = '2.0.12'; extends 'Lemonldap::NG::Portal::Main::Issuer'; @@ -82,11 +82,11 @@ sub computeGetParams { # Additional GET variables my %getPrms; if ( exists $self->conf->{issuerDBGetParameters} ) { - unless ( $req->urldc =~ m#^https?://([^/]+)# ) { + unless ( $req->urldc =~ URIRE ) { $self->logger->error("Malformed url $req->urldc"); return; } - my $vhost = $1; + my $vhost = $3 . ( $4 ? ":$4" : '' ); my $prms = $self->conf->{issuerDBGetParameters}->{$vhost}; unless ($prms) { $self->logger->warn("IssuerGet: $vhost has no configuration"); diff --git a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Lib/Choice.pm b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Lib/Choice.pm index d5201fece9b2849a019662c3c059134ccb37205d..215b0f54a848bdd736758512722c51579376900d 100644 --- a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Lib/Choice.pm +++ b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Lib/Choice.pm @@ -9,10 +9,10 @@ with 'Lemonldap::NG::Portal::Lib::OverConf'; our $VERSION = '2.0.11'; -has modules => ( is => 'rw', default => sub { {} } ); -has rules => ( is => 'rw', default => sub { {} } ); -has type => ( is => 'rw' ); -has catch => ( is => 'rw', default => sub { {} } ); +has modules => ( is => 'rw', default => sub { {} } ); +has rules => ( is => 'rw', default => sub { {} } ); +has type => ( is => 'rw' ); +has catch => ( is => 'rw', default => sub { {} } ); has sessionKey => ( is => 'ro', default => '_choice' ); my $_choiceRules; diff --git a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Main/Constants.pm b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Main/Constants.pm index 5cdcdad3e2205bd42113e5b8e41e68fa68dba2cd..44f1899fcd21ef953716b464736466a5431d4f0a 100644 --- a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Main/Constants.pm +++ b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Main/Constants.pm @@ -7,6 +7,8 @@ use Exporter 'import'; our $VERSION = '2.0.12'; use constant HANDLER => 'Lemonldap::NG::Handler::PSGI::Main'; +use constant URIRE => +qr{(((?^:https?))://((?:(?:(?:(?:(?:[a-zA-Z0-9][-a-zA-Z0-9]*)?[a-zA-Z0-9])[.])*(?:[a-zA-Z][-a-zA-Z0-9]*[a-zA-Z0-9]|[a-zA-Z])[.]?)|(?:[0-9]+[.][0-9]+[.][0-9]+[.][0-9]+)))(?::((?:[0-9]*)))?(/(((?:(?:(?:(?:[a-zA-Z0-9\-_.!~*'():@&=+$,]+|(?:%[a-fA-F0-9][a-fA-F0-9]))*)(?:;(?:(?:[a-zA-Z0-9\-_.!~*'():@&=+$,]+|(?:%[a-fA-F0-9][a-fA-F0-9]))*))*)(?:/(?:(?:(?:[a-zA-Z0-9\-_.!~*'():@&=+$,]+|(?:%[a-fA-F0-9][a-fA-F0-9]))*)(?:;(?:(?:[a-zA-Z0-9\-_.!~*'():@&=+$,]+|(?:%[a-fA-F0-9][a-fA-F0-9]))*))*))*))(?:[?]((?:(?:[;/?:@&=+$,a-zA-Z0-9\-_.!~*'()]+|(?:%[a-fA-F0-9][a-fA-F0-9]))*)))?))?)}; use constant { PE_IDPCHOICE => -5, PE_SENDRESPONSE => -4, @@ -224,6 +226,7 @@ sub portalConsts { our @EXPORT_OK = ( 'portalConsts', 'HANDLER', + 'URIRE', 'PE_IDPCHOICE', 'PE_SENDRESPONSE', 'PE_INFO', diff --git a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Main/Menu.pm b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Main/Menu.pm index 5a5468106c822881aee2d195d1dd0560a494efb0..d48f05753b2d6fb57387c26c937056c7ea116b45 100644 --- a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Main/Menu.pm +++ b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Main/Menu.pm @@ -5,8 +5,9 @@ package Lemonldap::NG::Portal::Main::Menu; use strict; use Mouse; use Clone 'clone'; +use Lemonldap::NG::Portal::Main::Constants 'URIRE'; -our $VERSION = '2.0.8'; +our $VERSION = '2.0.12'; extends 'Lemonldap::NG::Common::Module'; @@ -293,11 +294,11 @@ sub _buildApplicationHash { my $applications; # Get application items - my $appname = $apphash->{options}->{name} || $appid; - my $appuri = $apphash->{options}->{uri} || ""; + my $appname = $apphash->{options}->{name} || $appid; + my $appuri = $apphash->{options}->{uri} || ""; my $appdesc = $apphash->{options}->{description}; my $applogo = $apphash->{options}->{logo}; - my $apptip = $apphash->{options}->{tooltip} || $appname; + my $apptip = $apphash->{options}->{tooltip} || $appname; # Detect sub applications my $subapphash; @@ -393,9 +394,8 @@ sub _filterHash { # Check rights my $appdisplay = $apphash->{$key}->{options}->{display} || "auto"; - my ( $vhost, $appuri ) = - $apphash->{$key}->{options}->{uri} =~ m#^https?://([^/]*)(.*)#; - $vhost =~ s/:\d+$//; + $apphash->{$key}->{options}->{uri} =~ URIRE; + my ( $vhost, $appuri ) = ( $3, $5 ); $vhost = $self->p->HANDLER->resolveAlias($vhost); $appuri ||= '/'; diff --git a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Main/Process.pm b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Main/Process.pm index 3d260839e2f64c6fdfe5b7526a82e413f3c6f280..1d94092a6c11cb21eee2870d81c6998a6f9a4369 100644 --- a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Main/Process.pm +++ b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Main/Process.pm @@ -156,12 +156,16 @@ sub controlUrl { } # Unprotected hosts - my ( $proto, $vhost, $appuri ) = $tmp =~ m{^(https?://)([^/#?]*)(.*)}; - $vhost =~ s/:\d+$//; + unless ( $tmp =~ URIRE ) { + $self->userLogger->error("Bad URL $tmp"); + delete $req->{urldc}; + return PE_BADURL; + } + my ( $proto, $vhost, $appuri ) = ( $2, $3, $5 ); # Try to resolve alias my $originalVhost = $self->HANDLER->resolveAlias($vhost); - $vhost = $proto . $originalVhost; + $vhost = $proto . '://' . $originalVhost; $self->logger->debug( "Required URL (param: " . ( $req->param('logout') ? 'HTTP Referer' : 'urldc' ) . " | value: $tmp | alias: $vhost)" ); diff --git a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Main/Run.pm b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Main/Run.pm index 9d15c54da35eaeab4f96a4611c14bf790c434cd6..b118b99af70ff4a47cd6bae964d7f71001941c23 100644 --- a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Main/Run.pm +++ b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Main/Run.pm @@ -9,7 +9,7 @@ # package Lemonldap::NG::Portal::Main::Run; -our $VERSION = '2.0.10'; +our $VERSION = '2.0.12'; package Lemonldap::NG::Portal::Main; @@ -886,14 +886,16 @@ sub sendHtml { my $csp = $self->csp . "form-action " . $self->conf->{cspFormAction}; if ( my $url = $req->urldc ) { $self->logger->debug("Required urldc : $url"); - $url =~ s#(https?://[^/]+).*#$1#; + $url =~ URIRE; + $url = $2 . '://' . $3 . ( $4 ? ":$4" : '' ); $self->logger->debug("Set CSP form-action with urldc : $url"); $csp .= " $url"; } my $url = $args{params}->{URL}; if ( defined $url ) { $self->logger->debug("Required Params URL : $url"); - if ( $url =~ s#(https?://[^/]+).*#$1# ) { + if ( $url =~ URIRE ) { + $url = $2 . '://' . $3 . ( $4 ? ":$4" : '' ); $self->logger->debug("Set CSP form-action with Params URL : $url"); $csp .= " $url"; } diff --git a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Plugins/CDA.pm b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Plugins/CDA.pm index 81ad4fab64476ccf295d5f444a62b5af7ce67401..45f062f2361ef74603ad4a9f049216ddddec4eec 100644 --- a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Plugins/CDA.pm +++ b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Plugins/CDA.pm @@ -6,9 +6,10 @@ use Lemonldap::NG::Portal::Main::Constants qw( PE_APACHESESSIONERROR PE_ERROR PE_OK + URIRE ); -our $VERSION = '2.0.8'; +our $VERSION = '2.0.12'; extends 'Lemonldap::NG::Common::Module'; @@ -25,7 +26,8 @@ sub changeUrldc { my ( $self, $req ) = @_; my $urldc = $req->{urldc} || ''; if ( $req->id - and $urldc !~ m#^https?://[^/]*$self->{conf}->{domain}(:\d+)?/#oi + and $urldc =~ URIRE + and $3 !~ m@\Q$self->{conf}->{domain}\E$@oi and $self->p->isTrustedUrl($urldc) ) { my $ssl = $urldc =~ /^https/; diff --git a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Plugins/CheckDevOps.pm b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Plugins/CheckDevOps.pm index 53eb01ac3473d3c2d5905ac786500f367f455f4f..a8159f1d221d0ae563afc5ccde597cb9a8390f98 100644 --- a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Plugins/CheckDevOps.pm +++ b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Plugins/CheckDevOps.pm @@ -5,6 +5,7 @@ use Mouse; use JSON qw(from_json); use Lemonldap::NG::Common::UserAgent; use Lemonldap::NG::Portal::Main::Constants qw( + URIRE PE_OK PE_ERROR PE_BADURL @@ -124,27 +125,26 @@ sub run { # Check URL if allowed and exists if ( $self->conf->{checkDevOpsDownload} and $url = $req->param('url') ) { undef $url if $self->p->checkXSSAttack( 'CheckDevOps URL', $url ); - if ( $url && $url =~ m#^(?:https?://)?([^/]*)(.*)#i ) { + if ( $url && $url =~ URIRE ) { # Reformat url - my ( $vhost, $appuri ) = $url =~ m#^(?:https?://)?([^/]*)(.*)#i; - my ($proto) = $url =~ m#^(https?://).*#i; - $proto ||= 'http://'; - $url = "$proto$vhost/rules.json"; + my ( $proto, $vhost, $appuri ) = ( $2, $3, $5 ); + $url = "$proto://$vhost/rules.json"; my $resp = $self->ua->get( $url, 'Accept' => 'application/json' ); $self->logger->debug( "Code/Message from $url: " . $resp->code . '/' . $resp->message ); my $content = $resp->decoded_content; - $self->logger->debug("Content received from $url: $content") if $content; + $self->logger->debug("Content received from $url: $content") + if $content; if ( $resp->is_success ) { - $json = eval { from_json($content, { allow_nonref => 1 }) }; + $json = eval { from_json( $content, { allow_nonref => 1 } ) }; if ($@) { # Prepare form params undef $json; - $msg = 'PE' . PE_BAD_DEVOPS_FILE; + $msg = 'PE' . PE_BAD_DEVOPS_FILE; $self->userLogger->error( "CheckDevOps: bad 'rules.json' file retrieved from $url ($@)" ); @@ -153,7 +153,7 @@ sub run { else { # Prepare form params - $msg = 'PE' . PE_FILENOTFOUND; + $msg = 'PE' . PE_FILENOTFOUND; $self->userLogger->error( "CheckDevOps: Unable to download 'rules.json' file from $url" ); @@ -162,8 +162,8 @@ sub run { else { # Prepare form params - $msg = 'PE' . PE_BADURL; - $self->userLogger->error('CheckDevOps: bad provided URL'); + $msg = 'PE' . PE_BADURL; + $self->userLogger->error('CheckDevOps: bad URL provided'); } } unless ( $json || $msg ) { diff --git a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Plugins/CheckUser.pm b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Plugins/CheckUser.pm index ad63914c43dfc07798a9d6fa2404024a87cf9031..b34d88ce5c0a841f103986450d679fad00f44b64 100644 --- a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Plugins/CheckUser.pm +++ b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Plugins/CheckUser.pm @@ -192,7 +192,7 @@ sub check { }; return $self->p->sendJSONresponse( $req, $params ) if $req->wantJSON && $msg; - + # Display form return $self->p->sendHtml( $req, 'checkuser', params => $params ) if $msg; @@ -405,7 +405,7 @@ sub check { sub _resolveURL { my ( $self, $req, $url ) = @_; my ($proto) = $url =~ m#^(https?://).*#i; - my ( $vhost, $appuri ) = $url =~ m#^(?:https?://)?([^/]*)(.*)#i; + my ( $vhost, $appuri ) = $url =~ m@^(?:https?://)?([^/#]*)(.*)@i; my ($port) = $vhost =~ m#^.+(:\d+)$#; $port ||= ''; $vhost =~ s/:\d+$//; @@ -467,7 +467,7 @@ sub _userData { sub _authorization { my ( $self, $req, $uri, $attrs ) = @_; - my ( $vhost, $appuri ) = $uri =~ m#^https?://([^/]*)(.*)#; + my ( $vhost, $appuri ) = $uri =~ m@^https?://([^/#]*)(.*)@; my $exist = 0; $vhost =~ s/:\d+$//; @@ -489,7 +489,7 @@ sub _authorization { sub _headers { my ( $self, $req, $uri, $attrs, $savedUserData ) = @_; - my ($vhost) = $uri =~ m#^https?://([^/]*).*#; + my ($vhost) = $uri =~ m@^https?://([^/#]*).*@; $vhost =~ s/:\d+$//; $req->{env}->{HTTP_HOST} = $vhost; diff --git a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Plugins/RESTServer.pm b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Plugins/RESTServer.pm index 23379a4982f9affd5c60a836cb8325bf308875b9..7cc0595c1f9da1829d227247c114ee0c3e456a72 100644 --- a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Plugins/RESTServer.pm +++ b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Plugins/RESTServer.pm @@ -62,6 +62,7 @@ use Lemonldap::NG::Portal::Main::Constants qw( PE_OK portalConsts PE_PASSWORD_OK + URIRE ); our $VERSION = '2.0.12'; @@ -247,7 +248,7 @@ sub init { mysession => { ':sessionType' => 'updateMySession' }, ['PUT'] ); - extends @parents if ($add); + extends @parents if ($add); $self->setTypes( $self->conf ) if ( $self->conf->{restSessionServer} ); return 1; @@ -406,7 +407,8 @@ sub mysession { if ( $self->p->checkXSSAttack( 'authorizationfor', $req->urldc ) ); # Split URL - my ( $host, $uri ) = ( $req->urldc =~ m#^https?://([^/]+)(/.*)?$# ); + $req->urldc =~ URIRE; + my ( $host, $uri ) = ( $3 . ( $4 ? ":$4" : '' ), $5 ); $uri ||= '/'; return $self->p->sendError( $req, "Bad URL $req->{urldc}", 400 ) unless ($host); diff --git a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Plugins/SOAPServer.pm b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Plugins/SOAPServer.pm index 98548dade0acbfee22b4e1a765b517a92f450fba..be43cd3460b489cd093bf75433023eb1828ffb4b 100644 --- a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Plugins/SOAPServer.pm +++ b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Plugins/SOAPServer.pm @@ -16,6 +16,7 @@ use Mouse; use Lemonldap::NG::Portal::Main::Constants qw( PE_OK PE_FORMEMPTY + URIRE ); our $VERSION = '2.0.12'; @@ -458,7 +459,7 @@ sub isAuthorizedURI { my ( $self, $req, $id, $url ) = @_; die 'id is required' unless ($id); die 'uri is required' unless ($url); - die 'Bad uri' unless ( $url =~ m#^https?://([^/]+)(/.*)?$# ); + die 'Bad uri' unless ( $url =~ URIRE ); my ( $host, $uri ) = ( $1, $2 ); # Get user session. diff --git a/lemonldap-ng-portal/t/56-CheckDevOps-with-Download.t b/lemonldap-ng-portal/t/56-CheckDevOps-with-Download.t index 222499f496143ba1e526ed11a99ef394c48f1d10..fba55bf4e022da951f365e88dd95dd40cef36d61 100644 --- a/lemonldap-ng-portal/t/56-CheckDevOps-with-Download.t +++ b/lemonldap-ng-portal/t/56-CheckDevOps-with-Download.t @@ -143,9 +143,9 @@ ok( $res->{MSG} eq 'PE105', 'PE105' ) or print STDERR Dumper($res); count(4); -# Download file -# ------------- -$query = 'url=http://test3.example.com'; +# Bad URLs +# -------- +$query = 'url=test3.example.com'; ok( $res = $client->_post( '/checkdevops', @@ -157,19 +157,26 @@ ok( ); ok( $res = eval { from_json( $res->[2]->[0] ) }, 'Response is JSON' ) or print STDERR "$@\n" . Dumper($res); -ok( $res->{ALERTE} eq 'alert-info', 'alert-info found' ) +ok( $res->{MSG} eq 'PE37', 'Bad URL' ) or print STDERR Dumper($res); -ok( $res->{FILE} =~ /headers/, 'headers found' ) - or print STDERR Dumper($res); -ok( $res->{FILE} =~ /rules/, 'rules found' ) - or print STDERR Dumper($res); -ok( $res->{FILE} =~ /"\$uid ne qq#dwho#"/, 'rule found' ) - or print STDERR Dumper($res); -ok( $res->{URL} eq 'http://test3.example.com/rules.json', 'URL found' ) - or print STDERR Dumper($res); -ok( $res->{MSG} eq 'checkDevOps', 'MSG found' ) +count(3); + +# -------- +$query = 'url=http://test3.example.com#test'; +ok( + $res = $client->_post( + '/checkdevops', + IO::String->new($query), + cookie => "lemonldap=$id", + length => length($query), + ), + 'POST checkdevops with wrong url' +); +ok( $res = eval { from_json( $res->[2]->[0] ) }, 'Response is JSON' ) + or print STDERR "$@\n" . Dumper($res); +ok( $res->{URL} eq 'http://test3.example.com/rules.json', 'Well formated URL' ) or print STDERR Dumper($res); -count(8); +count(3); $client->logout($id); clean_sessions();