mirror of
https://github.com/webmin/webmin.git
synced 2026-10-29 06:31:04 +00:00
This PR fixes global and RPC controls appearing for group members even though saving ignores their edits. It detects group membership for existing users and clones, even when the editor cannot assign groups. The check also works for database-backed accounts without relying on local ACL files.
507 lines
16 KiB
Perl
Executable File
507 lines
16 KiB
Perl
Executable File
#!/usr/local/bin/perl
|
|
# edit_user.cgi
|
|
# Edit a new or existing webmin user
|
|
|
|
use strict;
|
|
use warnings;
|
|
no warnings 'redefine';
|
|
no warnings 'uninitialized';
|
|
require './acl-lib.pl'; ## no critic
|
|
our (%in, %text, %config, %gconfig, %access, $config_directory, $base_remote_user, $remote_user);
|
|
&foreign_require("webmin", "webmin-lib.pl");
|
|
|
|
&ReadParse();
|
|
my ($u, %user, $safe);
|
|
if ($in{'user'}) {
|
|
# Editing an existing user
|
|
&can_edit_user($in{'user'}) || &error($text{'edit_euser'});
|
|
&ui_print_header(undef, $text{'edit_title'}, "");
|
|
$u = &get_user($in{'user'});
|
|
$u || &error($text{'edit_egone'});
|
|
%user = %$u;
|
|
my %gacl = &get_module_acl($in{'user'}, '');
|
|
$safe = $gacl{'_safe'};
|
|
}
|
|
else {
|
|
# Creating a new user
|
|
$access{'create'} || &error($text{'edit_ecreate'});
|
|
if ($in{'clone'}) {
|
|
# Initial settings come from clone
|
|
$u = &get_user($in{'clone'});
|
|
%user = %$u;
|
|
delete($user{'name'});
|
|
my %gacl = &get_module_acl($in{'clone'}, '');
|
|
$safe = $gacl{'_safe'};
|
|
}
|
|
else {
|
|
# User starts out empty
|
|
%user = ( );
|
|
$safe = $in{'safe'};
|
|
}
|
|
&ui_print_header(undef, $safe ? $text{'edit_title3'}
|
|
: $text{'edit_title2'}, "");
|
|
}
|
|
my $me = &get_user($base_remote_user);
|
|
my %uaccess = &get_module_acl($in{'user'} || "", "", 1);
|
|
if (!$in{'user'} && $uaccess{'rpc'} == 2) {
|
|
# Don't offer the confusing 'root' or 'admin' RPC option by default
|
|
$uaccess{'rpc'} = 0;
|
|
}
|
|
if (!$in{'user'} && $safe) {
|
|
# Safe users should not inherit new permissions by default
|
|
$uaccess{'negative'} = 1;
|
|
}
|
|
|
|
# Give up if readonly
|
|
if ($user{'readonly'} && !$in{'readwrite'}) {
|
|
my %minfo = &get_module_info($user{'readonly'});
|
|
print &text('edit_readonly', $minfo{'desc'},
|
|
"edit_user.cgi?user=$in{'user'}&readwrite=1"),"<p>\n";
|
|
&ui_print_footer("", $text{'index_return'});
|
|
exit;
|
|
}
|
|
|
|
print &ui_form_start("save_user.cgi", "post");
|
|
if ($in{'user'}) {
|
|
print &ui_hidden("old", $user{'name'});
|
|
print &ui_hidden("oldpass", $user{'pass'});
|
|
}
|
|
if ($in{'clone'}) {
|
|
print &ui_hidden("clone", $in{'clone'});
|
|
}
|
|
print &ui_hidden("safe", $safe);
|
|
print &ui_hidden_table_start($text{'edit_rights'}, "width=100%", 2, "rights",
|
|
1, [ "width=30%" ]);
|
|
|
|
# Username
|
|
print &ui_table_row($text{'edit_user'},
|
|
$access{'rename'} || !$in{'user'} ?
|
|
&ui_textbox("name", $user{'name'}, 30,
|
|
0, undef, "autocomplete=off") : $user{'name'});
|
|
|
|
# Source user for clone
|
|
if ($in{'clone'}) {
|
|
print &ui_table_row($text{'edit_cloneof'}, "<tt>$in{'clone'}</tt>");
|
|
}
|
|
|
|
# Find and show parent group
|
|
my @glist = &list_groups();
|
|
my @mcan = $access{'gassign'} eq '*' ?
|
|
( ( map { $_->{'name'} } @glist ), '_none' ) :
|
|
split(/\s+/, $access{'gassign'});
|
|
my %gcan = map { $_, 1 } @mcan;
|
|
# Find the user's group even when this editor cannot change it.
|
|
my $memg;
|
|
foreach my $g (@glist) {
|
|
if (&indexof($user{'name'}, @{$g->{'members'}}) >= 0 ||
|
|
$in{'clone'} &&
|
|
&indexof($in{'clone'}, @{$g->{'members'}}) >= 0) {
|
|
# A clone starts in the same group as its source user.
|
|
$memg = $g;
|
|
}
|
|
}
|
|
# Show the group selector when this editor may change membership.
|
|
if (@glist && %gcan) {
|
|
my @opts = ( );
|
|
if ($gcan{'_none'}) {
|
|
# Allow leaving a group only when the editor has permission.
|
|
push(@opts, [ undef, "<$text{'edit_none'}>" ]);
|
|
}
|
|
# Keep the current group available even if it cannot be newly assigned.
|
|
foreach my $g (@glist) {
|
|
next if (!$gcan{$g->{'name'}} && $memg ne $g);
|
|
push(@opts, [ $g->{'name'} ]);
|
|
}
|
|
print &ui_table_row($text{'edit_group'},
|
|
&ui_select("group", $memg->{'name'}, \@opts));
|
|
}
|
|
|
|
# Show password type menu and current password
|
|
my $passmode = !$in{'user'} ? 0 :
|
|
$user{'pass'} eq 'x' ? 3 :
|
|
$user{'sync'} ? 2 :
|
|
$user{'pass'} eq 'e' ? 5 :
|
|
$user{'pass'} eq '*LK*' ? 4 : 1;
|
|
my %miniserv;
|
|
&get_miniserv_config(\%miniserv);
|
|
my @opts = ( [ 0, "$text{'edit_set'} .." ] );
|
|
if ($in{'user'}) {
|
|
push(@opts, [ 1, $text{'edit_dont'} ]);
|
|
}
|
|
push(@opts, [ 3, $text{'edit_unix'} ]);
|
|
if ($user{'sync'}) {
|
|
push(@opts, [ 2, $text{'edit_same'} ]);
|
|
}
|
|
if ($miniserv{'extauth'}) {
|
|
push(@opts, [ 5, $text{'edit_extauth'} ]);
|
|
}
|
|
push(@opts, [ 4, $text{'edit_lock'} ]);
|
|
my ($lockbox, $tempbox) = ("", "");
|
|
if ($passmode == 1) {
|
|
$lockbox = &ui_checkbox("lock", 1, $text{'edit_templock'},
|
|
$user{'pass'} =~ /^\!/ ? 1 : 0);
|
|
}
|
|
if ($passmode != 3 && $passmode != 4) {
|
|
$tempbox = &ui_checkbox("temp", 1, $text{'edit_temppass'},
|
|
$user{'temppass'});
|
|
}
|
|
my $expmsg = "";
|
|
if ($user{'lastchange'} && $miniserv{'pass_maxdays'}) {
|
|
my $daysold = int((time() - $user{'lastchange'})/(24*60*60));
|
|
if ($miniserv{'pass_lockdays'} &&
|
|
$daysold > $miniserv{'pass_lockdays'}) {
|
|
$expmsg = "<br>"."<font color=#ff0000>".
|
|
&text('edit_passlocked', $daysold)."</font>";
|
|
}
|
|
elsif ($daysold > $miniserv{'pass_maxdays'}) {
|
|
$expmsg = "<br>"."<font color=#ffaa00>".
|
|
&text('edit_passmax', $daysold)."</font>";
|
|
}
|
|
elsif ($daysold) {
|
|
$expmsg = "<br>".&text('edit_passold', $daysold);
|
|
}
|
|
else {
|
|
$expmsg = "<br>".$text{'edit_passtoday'};
|
|
}
|
|
}
|
|
my $js = "onChange='form.pass.disabled = value != 0;'";
|
|
print &ui_table_row($text{'edit_pass'},
|
|
&ui_select("pass_def", $passmode, \@opts, 1, 0, 0, 0, $js)." ".
|
|
&ui_password("pass", undef, 25, $passmode != 0, undef,
|
|
"autocomplete=off").
|
|
($lockbox || $tempbox ? "<br>" : "").$lockbox.$tempbox.$expmsg);
|
|
|
|
# Real name
|
|
print &ui_table_row($text{'edit_real'},
|
|
&ui_textbox("real", $user{'real'}, 60));
|
|
|
|
# Contact email for recovery
|
|
print &ui_table_row($text{'edit_email'},
|
|
&ui_textbox("email", $user{'email'}, 60));
|
|
|
|
# Storage type
|
|
if ($in{'user'}) {
|
|
print &ui_table_row($text{'edit_proto'},
|
|
$text{'edit_proto_'.($user{'proto'} || '')});
|
|
}
|
|
|
|
# Safe or not?
|
|
my $smsg;
|
|
if ($in{'user'} && $safe) {
|
|
$smsg = &ui_radio("unsafe", 0, [ [ 0, $text{'edit_safe1'} ],
|
|
[ 1, $text{'edit_safe0'} ] ]);
|
|
}
|
|
else {
|
|
$smsg = $safe ? $text{'edit_safe1'} : $text{'edit_safe0'};
|
|
}
|
|
print &ui_table_row($text{'edit_safe'}, $smsg);
|
|
|
|
print &ui_hidden_table_end("rights");
|
|
|
|
# Start of UI options section
|
|
my $showui = $access{'chcert'} || $access{'lang'} ||
|
|
$access{'cats'} || $access{'theme'};
|
|
if ($showui) {
|
|
print &ui_hidden_table_start($text{'edit_ui'}, "width=100%", 2, "ui",
|
|
0, [ "width=30%" ]);
|
|
}
|
|
|
|
if ($access{'chcert'}) {
|
|
# SSL certificate name
|
|
print &ui_table_row($text{'edit_cert'},
|
|
&ui_opt_textbox("cert", $user{'cert'}, 50, $text{'edit_none'}));
|
|
}
|
|
|
|
if ($access{'lang'}) {
|
|
# Current language
|
|
my $ulang = safe_language($user{'lang'});
|
|
print &ui_table_row($text{'edit_lang'},
|
|
&ui_radio("lang_def", $ulang ? 0 : 1,
|
|
[ [ 1, $text{'default'} ],
|
|
[ 0, &ui_select("lang", $ulang,
|
|
[ map { [ $_->{'lang'}, $_->{'desc'}."" ] }
|
|
&list_languages() ]) ]
|
|
]));
|
|
}
|
|
|
|
if ($access{'locale'}) {
|
|
# Current locale
|
|
eval { require DateTime; DateTime->import;
|
|
require DateTime::Locale; DateTime::Locale->import;
|
|
require DateTime::TimeZone; DateTime::TimeZone->import;
|
|
1 };
|
|
if (!$@ && $] > 5.011) {
|
|
my $locales = &list_locales();
|
|
my %localesrev = reverse %{$locales};
|
|
my $locale_auto = &parse_accepted_language();
|
|
print &ui_table_row($text{'edit_locale'},
|
|
&ui_radio("locale_def", $user{'locale'} ? 0 : 1,
|
|
[ [ 1, $text{'default'} ],
|
|
[ 0, &ui_select("locale", $user{'locale'} || $gconfig{'locale'} || &get_default_system_locale(),
|
|
[ map { [ $localesrev{$_}, $_ ] } sort values %{$locales} ]) ] ]),
|
|
undef, [ "valign=middle","valign=middle" ]);
|
|
}
|
|
}
|
|
|
|
if ($access{'cats'}) {
|
|
# Show categorized modules?
|
|
print &ui_table_row($text{'edit_notabs'},
|
|
&ui_radio("notabs", $user{'notabs'} || 0,
|
|
[ [ 1, $text{'yes'} ],
|
|
[ 2, $text{'no'} ],
|
|
[ 0, $text{'default'} ] ]));
|
|
}
|
|
|
|
my @all = &webmin::list_visible_themes($user{'theme'});
|
|
my @themes = grep { !$_->{'overlay'} } @all;
|
|
my @overlays = grep { $_->{'overlay'} } @all;
|
|
|
|
if ($access{'theme'}) {
|
|
my $tconf_link;
|
|
my %tinfo = &webmin::get_theme_info($user{'theme'});
|
|
if ($user{'theme'} && $user{'theme'} eq $tinfo{'dir'} &&
|
|
$user{'name'} eq $remote_user &&
|
|
$tinfo{'config_link'}) {
|
|
$tconf_link = &ui_tag('span', &ui_link(
|
|
"@{[&get_webprefix()]}/$tinfo{'config_link'}",
|
|
&ui_tag('span', '⚙',
|
|
{ class => 'theme-config-char',
|
|
title => $text{'themes_configure'} }),
|
|
'text-link'), { style => 'position: relative;' });
|
|
}
|
|
# Current theme
|
|
my @topts = ( );
|
|
push(@topts, !$user{'theme'} ? [ '', $text{'edit_themedef'} ] : ());
|
|
foreach my $t (@themes) {
|
|
push(@topts, [ $t->{'dir'}, $t->{'desc'} ]);
|
|
}
|
|
print &ui_table_row($text{'edit_theme'},
|
|
&ui_radio("theme_def", defined($user{'theme'}) ? 0 : 1,
|
|
[ [ 1, $text{'edit_themeglobal'} ],
|
|
[ 0, &ui_select("theme", $user{'theme'}, \@topts).
|
|
$tconf_link ] ]));
|
|
}
|
|
|
|
if ($access{'theme'} && @overlays) {
|
|
# Overlay theme, if any
|
|
print &ui_table_row($text{'edit_overlay'},
|
|
&ui_radio("overlay_def", defined($user{'overlay'}) ? 0 : 1,
|
|
[ [ 1, $text{'edit_overlayglobal'} ],
|
|
[ 0, &ui_select("overlay", $user{'overlay'},
|
|
[ map { [ $_->{'dir'}, $_->{'desc'} ] } @overlays ]
|
|
) ] ]));
|
|
}
|
|
|
|
if ($showui) {
|
|
print &ui_hidden_table_end("ui");
|
|
}
|
|
|
|
# Start of security options section
|
|
my $showsecurity = $access{'logouttime'} || $access{'ips'} ||
|
|
$access{'minsize'} ||
|
|
$access{'times'};
|
|
if ($showsecurity) {
|
|
print &ui_hidden_table_start($text{'edit_security'}, "width=100%", 2,
|
|
"security", 0, [ "width=30%" ]);
|
|
}
|
|
|
|
if ($access{'logouttime'}) {
|
|
# Show logout time
|
|
print &ui_table_row($text{'edit_logout'},
|
|
&ui_opt_textbox("logouttime", $user{'logouttime'}, 5,
|
|
$text{'default'})." ".$text{'edit_mins'});
|
|
}
|
|
|
|
if ($access{'minsize'}) {
|
|
# Show minimum password length, for just this user
|
|
print &ui_table_row($text{'edit_minsize'},
|
|
&ui_opt_textbox("minsize", $user{'minsize'}, 5,
|
|
$text{'default'})." ".$text{'edit_chars'});
|
|
}
|
|
|
|
if ($access{'nochange'} && $miniserv{'pass_maxdays'}) {
|
|
# Opt out of forced password change, for this user
|
|
print &ui_table_row($text{'edit_nochange'},
|
|
&ui_radio("nochange", $user{'nochange'},
|
|
[ [ 0, $text{'yes'} ], [ 1, $text{'no'} ] ]));
|
|
}
|
|
|
|
if ($access{'ips'}) {
|
|
# Allowed IP addresses
|
|
print &ui_table_row(&hlink("<b>$text{'edit_ips'}</b>", "ips"),
|
|
&ui_radio("ipmode", $user{'allow'} ? 1 :
|
|
$user{'deny'} ? 2 : 0,
|
|
[ [ 0, $text{'edit_all'}."<br>" ],
|
|
[ 1, $text{'edit_allow'}."<br>" ],
|
|
[ 2, $text{'edit_deny'}."<br>" ] ]).
|
|
&ui_textarea("ips",
|
|
join("\n", split(/\s+/, $user{'allow'} ||
|
|
$user{'deny'} || "")),
|
|
4, 30));
|
|
}
|
|
|
|
if ($access{'times'}) {
|
|
# Show allowed days of the week
|
|
my %days = map { $_, 1 } split(/,/, $user{'days'} || '');
|
|
my $daysels = "";
|
|
for(my $i=0; $i<7; $i++) {
|
|
$daysels .= &ui_checkbox("days", $i, $text{'day_'.$i},
|
|
$days{$i});
|
|
}
|
|
print &ui_table_row($text{'edit_days'},
|
|
&ui_radio("days_def", !defined($user{'days'}) || $user{'days'} eq '' ? 1 : 0,
|
|
[ [ 1, $text{'edit_alldays'} ],
|
|
[ 0, $text{'edit_seldays'} ] ])."<br>".
|
|
$daysels);
|
|
|
|
# Show allow hour/minute range
|
|
my ($hf, $mf) = split(/\./, $user{'hoursfrom'} || '');
|
|
my ($ht, $mt) = split(/\./, $user{'hoursto'} || '');
|
|
print &ui_table_row($text{'edit_hours'},
|
|
&ui_radio("hours_def", !defined($hf) || $hf eq '' ? 1 : 0,
|
|
[ [ 1, $text{'edit_allhours'} ],
|
|
[ 0, &text('edit_selhours',
|
|
&ui_textbox("hours_hfrom", $hf, 2),
|
|
&ui_textbox("hours_mfrom", $mf, 2),
|
|
&ui_textbox("hours_hto", $ht, 2),
|
|
&ui_textbox("hours_mto", $mt, 2)) ] ]));
|
|
}
|
|
|
|
# Two-factor details
|
|
if ($user{'twofactor_provider'}) {
|
|
my ($prov) = grep { $_->[0] eq $user{'twofactor_provider'} }
|
|
&webmin::list_twofactor_providers();
|
|
print &ui_table_row($text{'edit_twofactor'},
|
|
&text('edit_twofactorprov', "<i>$prov->[1]</i>",
|
|
"<tt>$user{'twofactor_id'}</tt>")."<br>\n".
|
|
&ui_checkbox('cancel', 1, $text{'edit_twofactorcancel'}, 0));
|
|
}
|
|
elsif ($miniserv{'twofactor_provider'}) {
|
|
print &ui_table_row($text{'edit_twofactor'},
|
|
$text{'edit_twofactornone'}." ".
|
|
&ui_submit($text{'edit_twofactoradd'}, "twofactor"));
|
|
}
|
|
|
|
# RPC is a global permission and cannot be edited on a group member.
|
|
if ($access{'acl'} && !$memg->{'name'} && !$safe) {
|
|
print &ui_table_row(&hlink($text{'acl_rpc'}, 'rpc'),
|
|
&ui_radio("rpc", int($uaccess{'rpc'}),
|
|
[ [ 1, $text{'acl_rpc1'} ],
|
|
$uaccess{'rpc'} == 2 ? ( [ 2, $text{'acl_rpc2'} ] ) : ( ),
|
|
[ 3, $text{'acl_rpc3'} ],
|
|
[ 0, $text{'acl_rpc0'} ] ]));
|
|
}
|
|
|
|
print &ui_hidden_table_end("security");
|
|
|
|
# Work out which modules can be selected
|
|
@mcan = $access{'mode'} == 1 ? @{$me->{'modules'}} :
|
|
$access{'mode'} == 2 ? split(/\s+/, $access{'mods'}) :
|
|
&list_modules();
|
|
my (%mcan, %has);
|
|
map { $mcan{$_}++ } @mcan;
|
|
map { $has{$_}++ } @{$user{'modules'}};
|
|
|
|
# Start of modules section
|
|
my @groups = &list_groups();
|
|
print &ui_hidden_table_start(@groups ? $text{'edit_modsg'} : $text{'edit_mods'},
|
|
"width=100%", 2, "mods");
|
|
|
|
# Build list of modules, based on safe mode
|
|
my @allmods = &list_module_infos();
|
|
if ($safe) {
|
|
@allmods = grep { $has{$_->{'dir'}} ||
|
|
&get_safe_acl($_->{'dir'}) } @allmods;
|
|
}
|
|
|
|
# Show available modules, under categories
|
|
my @mlist = grep { $access{'others'} || $has{$_->{'dir'}} ||
|
|
$mcan{$_->{'dir'}} } @allmods;
|
|
my @links = ( &select_all_link("mod", 0, $text{'edit_selall'}),
|
|
&select_invert_link("mod", 0, $text{'edit_invert'}) );
|
|
my @cats = &unique(map { $_->{'category'} || '' } @mlist);
|
|
my %catnames;
|
|
&read_file("$config_directory/webmin.catnames", \%catnames);
|
|
my $grids = "";
|
|
foreach my $c (sort { $b cmp $a } @cats) {
|
|
my @cmlist = grep { ($_->{'category'} || '') eq $c } @mlist;
|
|
$grids .= "<b>".($catnames{$c} ||
|
|
$text{'category_'.$c} || '')."</b><br>\n";
|
|
my @grid = ( );
|
|
my $sw = 0;
|
|
foreach my $m (@cmlist) {
|
|
next if ($m->{'noacl'});
|
|
my $md = $m->{'dir'};
|
|
my $fromgroup = $memg &&
|
|
&indexof($md, @{$memg->{'modules'}}) >= 0;
|
|
if ($mcan{$md} && $fromgroup) {
|
|
# Module comes from group
|
|
push(@grid, (sprintf "<img src=images/%s.gif> %s\n",
|
|
$has{$md} ? 'tick' : 'empty', $m->{'desc'}).
|
|
($has{$md} ? &ui_hidden("mod", $md) : ""));
|
|
}
|
|
elsif ($mcan{$md}) {
|
|
my $label;
|
|
if ($access{'acl'} && $in{'user'} && !$safe &&
|
|
&can_module_acl($m)) {
|
|
# Show link for editing ACL
|
|
$label = ui_link("edit_acl.cgi?" .
|
|
"mod=" . urlize($m->{'dir'}) .
|
|
"&user=". urlize($in{'user'}),
|
|
$m->{'desc'}) . "\n";
|
|
}
|
|
else {
|
|
# No privileges to edit ACL
|
|
$label = $m->{'desc'};
|
|
}
|
|
push(@grid, &ui_checkbox("mod", $md, $label,$has{$md}));
|
|
}
|
|
else {
|
|
push(@grid, (sprintf "<img src=images/%s.gif> %s\n",
|
|
$has{$md} ? 'tick' : 'empty', $m->{'desc'}));
|
|
}
|
|
}
|
|
$grids .= &ui_grid_table(\@grid, 2, 100, [ "width=50%", "width=50%" ]);
|
|
}
|
|
|
|
print &ui_table_row(undef, &ui_links_row(\@links).
|
|
$grids.
|
|
&ui_links_row(\@links), 2);
|
|
print &ui_hidden_table_end("mods");
|
|
|
|
# Group members inherit global permissions, even without a local ACL file.
|
|
# Hide these fields because save_user.cgi ignores individual edits.
|
|
if ($access{'acl'} && !$memg->{'name'} && !$safe) {
|
|
print &ui_hidden_table_start($text{'edit_global'}, "width=100%", 2,
|
|
"global", 0, [ "width=30%" ]);
|
|
print &ui_hidden("acl_security_form", 1);
|
|
&foreign_require("", "acl_security.pl");
|
|
&foreign_call("", "acl_security_form", \%uaccess);
|
|
print &ui_hidden_table_end("global");
|
|
}
|
|
|
|
# Generate form end buttons
|
|
my @buts = ( );
|
|
push(@buts, [ undef, $in{'user'} ? $text{'save'} : $text{'create'} ]);
|
|
if ($in{'user'}) {
|
|
if ($access{'create'}) {
|
|
push(@buts, [ "but_clone", $text{'edit_clone'} ]);
|
|
}
|
|
if (&foreign_available("webminlog")) {
|
|
push(@buts, [ "but_log", $text{'edit_log'} ]);
|
|
}
|
|
if ($access{'switch'} && $main::session_id && $in{'user'} ne $remote_user) {
|
|
push(@buts, [ "but_switch", $text{'edit_switch'} ]);
|
|
}
|
|
if ($gconfig{'forgot_pass'}) {
|
|
push(@buts, [ "but_forgot", $text{'edit_forgot'} ]);
|
|
}
|
|
if ($access{'delete'}) {
|
|
push(@buts, [ "but_delete", $text{'delete'} ]);
|
|
}
|
|
}
|
|
print &ui_form_end(\@buts);
|
|
|
|
&ui_print_footer("", $text{'index_return'});
|
|
|