JFIFxxC      C  " }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr# # Authentic Theme (https://github.com/authentic-theme/authentic-theme) # Copyright Ilia Rostovtsev # Licensed under MIT (https://github.com/authentic-theme/authentic-theme/blob/master/LICENSE) # use strict; our (%in, %gconfig, $root_directory, $remote_user, $get_user_level, %theme_config, $theme_info, %theme_text, $current_theme, $has_usermin); sub xhr { my $type = $in{'type'}; my $subtype = $in{'subtype'}; my $action = $in{'action'}; my %data = (); my $output = sub { my ($data) = @_; # Set no links header print "x-no-links: 1\n"; # Return fetched data if any print_json($data); }; if ($type eq "data") { if ($subtype eq "theme") { # List theme hotkeys if ($action eq "list-hotkeys") { do("$ENV{'THEME_ROOT'}/tconfig-lib.pl"); my @hotkeys_labels = ($theme_text{'settings_right_hotkey_options'}, $theme_text{'settings_right_hotkey_custom_options'}); my $settings_data = theme_settings_data(); my @config_quick_access = @{ $settings_data->{'config_quick_access'} }; my @hotkeys_global; # Theme hotkeys foreach my $opt (@config_quick_access) { next if (&indexof($opt->{'data'}->{'category'}, @hotkeys_labels) < 0); next if ( &indexof($opt->{'key'}, ('settings_hotkeys_active', 'settings_hotkey_toggle_hold_modifier', 'settings_hotkey_toggle_modifier', ) ) >= 0); push(@hotkeys_global, { key => $opt->{'key'}, title => $opt->{'value'}, section => $opt->{'section'} }); } $data{'hotkeys-global'} = \@hotkeys_global; # File Manager hotkeys my $file_manager = read_help_file($current_theme, 'file-manager'); my @file_manager_hotkeys_labels = $file_manager =~ /(.*?)<\//gms; my @file_manager_hotkeys_values = $file_manager =~ /(.*?)<\//gms; my %file_manager_hotkeys_map; @file_manager_hotkeys_map{ (@file_manager_hotkeys_values) } = (@file_manager_hotkeys_labels); my @hotkeys_file_manager; foreach my $value (@file_manager_hotkeys_values) { push(@hotkeys_file_manager, { key => $file_manager_hotkeys_map{$value}, title => $value }); } $data{'hotkeys-file-manager'} = \@hotkeys_file_manager; my @hotkeys_editor; my $editor = read_help_file($current_theme, 'editor'); my @editor_hotkeys_labels = $editor =~ /(.*?)<\//gms; my @editor_hotkeys_values = $editor =~ /(.*?)<\//gms; my %editor_hotkeys_map; @editor_hotkeys_map{(@editor_hotkeys_values)} = (@editor_hotkeys_labels); foreach my $value (@editor_hotkeys_values) { push(@hotkeys_editor, { key => $editor_hotkeys_map{$value}, title => $value }); } $data{'hotkeys-editor'} = \@hotkeys_editor; } # Control theme settings if ($action eq "settings") { my $do = $in{'do'}; if ($do eq 'save') { theme_config_save(); } elsif ($do eq 'restore') { theme_config_restore(); } } } } if ($type eq "cmd") { # Fail state restart if ($action eq "restart") { if (webmin_user_is_admin()) { my $systemd = has_command('systemctl'); if ($systemd) { # We need to force kill a potentially stuck process without pid my %miniserv; get_miniserv_config(\%miniserv); my $force_restart = -r $miniserv{'pidfile'} ? "${systemd} kill -s SIGTERM webmin" : "/etc/webmin/stop ; /etc/webmin/start ; /etc/webmin/.stop-init --kill"; system($force_restart); } else { restart_miniserv(); } } } } if ($type eq 'nav') { # Returns navigation menu available for requested domain/server if ($action eq 'validate') { my $module = $in{'module'}; my $param = $in{'param'}; my @menu = list_combined_webmin_menu(undef, \%in, $module); # Returns a list of allowed domain/server related links if ($subtype eq 'links') { my @submenu = map { $_->{'link'} =~ /.*?$module.*\/(\w+\.cgi).*?$param=/, $_->{'link'} =~ /(\/.*?_log\.cgi\?.*)/, $_->{'link'} =~ /(.*?\/webminlog\/.*?\.cgi.*)/, $_->{'link'} =~ /(.*?\/phpini\/.*?\.cgi.*)/, $_->{'link'} =~ /(.*?\/spam\/.*?\.cgi.*)/, $_->{'link'} =~ /(.*?\/apache\/.*?\.cgi.*)/, $_->{'link'} =~ /(.*?\/virtualmin-.*?\/.*?\.cgi.*)/, } array_flatten(grep {$_->[0]->{'link'}} map {$_->{'members'}} @menu); # Always forbidden Delete Server page when switching domains @submenu = grep {$_ !~ /delete_domain.cgi/} @submenu if (@submenu); my @fmmenu = map {$_->{'link'} =~ /(filemin\/.*?\.cgi.*)/} @menu; @menu = map {$_->{'link'} =~ /.*?$module.*\/(\w+\.cgi).*?$param=/} @menu; @menu = (@menu, @submenu, @fmmenu); $data{'menu'} = \@menu; } } # Returns default goto if set if ($action eq 'goto') { # Validate if default goto is allowed for the given user my $mod_def = get_default_module(); if ($mod_def) { $data{'gotomodule'} = "$mod_def"; } } # Returns requested navigation if ($action eq 'get') { require("$ENV{'THEME_ROOT'}/navigation-lib.pl"); my ($tab, $page) = nav_detector(); if ($subtype eq 'cloudmin') { $data{'menu'} = nav_cloudmin_menu($page); } elsif ($subtype eq 'virtualmin') { $data{'menu'} = nav_virtualmin_menu($page); } elsif ($subtype eq 'webmail') { $data{'menu'} = nav_mailbox_menu($page); } else { $data{'menu'} = nav_webmin_menu($page); } } } # Check if action is allowed if ($type eq 'can') { if ($action eq 'view_dom') { require("$ENV{'THEME_ROOT'}/navigation-lib.pl"); $data{$action} = nav_virtualmin_domain_available($in{'dom'}, 'id'); } } if ($type eq 'file') { if ($action eq 'cache') { if ($in{'module'} eq 'virtual-server') { if ($in{'submodule'} eq 'server-templates') { if (foreign_available('virtual-server')) { foreign_require("virtual-server"); my $var_dir = $virtual_server::module_var_directory; my $server_template_id = int($in{'server-template-id'}); my $server_template_id_user_file = "$var_dir/$in{'module'}-$in{'submodule'}-$server_template_id.$remote_user"; if ($in{'subaction'} eq 'get') { if (-r $server_template_id_user_file) { $data{'cached'} = unserialise_variable(read_file_contents($server_template_id_user_file)); } } elsif ($in{'subaction'} eq 'put') { my $data = convert_from_json($in{'data'}); write_file_contents($server_template_id_user_file, serialise_variable($data)); &$output(\%data); exit; } } } } } if ($action eq 'motd') { # Get current user motd file if ($subtype eq 'get') { $data{'motd'} = get_all_users_motd_data($remote_user); } # Save current user motd file if ($subtype eq 'set' && webmin_user_is_admin()) { my $data = convert_from_json($in{'data'}); put_user_motd($data); } # Get current user motd sent messages if ($subtype eq 'receive') { $data{'motd'} = get_all_users_motd_data(); } } # Generate given file info if ($action eq 'stat') { my ($module, $sumtype, $jailed_user, $jailed_user_home, $cfile, $mime, $dir, $fzi, $fz, $fzx, $ft, $s, $sz, $nz); $module = 'filemin'; # $in{'module'}; if (!foreign_available($module)) { $data{'module-access-denied'} = $module; &$output(\%data); exit; } $cfile = $in{'file'}; $sumtype = $in{'checksum'}; $jailed_user = get_fm_jailed_user($module, 1); $jailed_user_home = get_fm_jailed_user($module); if ($jailed_user) { switch_to_given_unix_user($jailed_user); $cfile = $jailed_user_home . $cfile; } else { switch_to_remote_user_safe(); } my $get_file_checksum = sub { my ($cfile, $cmd) = @_; my $sum = 0; my @allowed_checksum_cmds = ('md5sum', 'sha1sum', 'sha256sum'); foreach my $c (@allowed_checksum_cmds) { if ($cmd eq $c) { if (has_command($c)) { $sum = backquote_command("$c " . quotemeta($cfile) . " 2>/dev/null"); $sum =~ s/(\S+)(\s+)(.*)/$1/; $sum = trim($sum); } else { $sum = -1; } } } return $sum; }; # Get given checksum and exit if ($sumtype) { my $sum = &$get_file_checksum($cfile, $sumtype); $data{'checksum'} = $sum; &$output(\%data); exit; } # Build extended file stats $fzi = recursive_disk_usage($cfile); $dir = -d $cfile; $fz = $fzi; $fz = nice_size($fz, -1); $fzx = ($fz =~ /$theme_text{'nice_size_b'}/); $ft = backquote_command("file -b " . quotemeta($cfile) . " 2>/dev/null"); $s = backquote_command("stat " . quotemeta($cfile) . " 2>/dev/null"); $ft = trim($ft); $s =~ /(Size:)(\s+)(\d+)(\s+)/; $sz = length($3) + length($4); $nz = length($fz); $sz -= $nz; $sz = " " x ($sz + 2); $s =~ s/(Size:)(\s+)(\d+)(\s+)/$fzx ? "$1$2$fz$sz" : "$1$2$fz ($3 $theme_text{'nice_size_b'})$sz"/e; if (!$dir) { $mime = guess_mime_type($cfile, -1); if ($mime == -1) { $mime = undef; } else { $mime = " ($mime) "; } } $s =~ s/(File:)(.*)\n/$1$2\n Type: $ft\n/ if ($ft); $s =~ s/(File:)(\s+)(.*)/$1$2$cfile$mime/; $s =~ s/(Birth:\s+-.*[\n\s]+)//m; $s =~ s/\((\s*)(\d+\/)\s*(.*?)\)/($2$3)/g; my $lsattr_cmd = has_command('lsattr'); if ($lsattr_cmd) { my $lsattr; my $lsattr_param = $dir ? " -d" : undef; $lsattr = backquote_command("$lsattr_cmd$lsattr_param " . quotemeta($cfile) . " 2>/dev/null"); $lsattr =~ s/(\S+)(\s+)(.*)/$1/; $s =~ s/(Links:)(.*)\n/$1$2\n Attrs: $lsattr/ if ($lsattr); } my $getfacl_cmd = has_command('getfacl'); if ($getfacl_cmd) { my $lbl = $lsattr_cmd ? "Attrs:" : "Links:"; my $getfacl = backquote_command("$getfacl_cmd -p " . quotemeta($cfile) . " 2>/dev/null"); my @getfacls = ($getfacl =~ /^(?!(#|user::|group::|other::))([\w\:\-\_]+)/gm); $getfacl = join(' ', @getfacls); $s =~ s/($lbl)(.*)\n/$1$2\n ACLs:$getfacl\n/ if ($getfacl); } if (!$dir) { my @csums = ('md5sum', 'sha1sum', 'sha256sum'); foreach my $c (@csums) { my ($sp, $sumv, $sum, $sumn); $sum = 'data-a-checksum="' . $c . '"'; $sumn = $c; $sumn =~ s/sum//; $sp = " " x (6 - length($sumn)); if ($fzi < 1024000) { $sumv = &$get_file_checksum($cfile, $c); $sum = $sumv if ($sumv != -1); } $s = rtrim($s); $s = "$s\n"; $s .= "$sp$sumn: $sum\n" if ($sumv != -1); } } $data{'content'} = rtrim($s); $data{'size'} = [$fz, $fzi]; } } # Legacy calls from index page if (post_has('xhr-')) { head(); if ($in{'xhr-get_available_modules'} eq '1') { print get_available_modules('json'); } # This should be split on next refactor to be used separately by modules (filemin/mailbox) elsif ($in{'xhr-get_size'} eq '1') { switch_to_remote_user_safe(); my $nodir = $in{'xhr-get_size_nodir'}; my $path = get_access_data('root') . $in{'xhr-get_size_path'}; my $home = get_user_home(); my $module = $in{'xhr-get_size_cmodule'}; # $in{'xhr-get_size_cmodule'}; if ($module eq 'filemin') { exit if (!foreign_available($module)); my $jailed_user = get_fm_jailed_user($module); if ($jailed_user) { $home = $jailed_user; $path = $home . $in{'xhr-get_size_path'}; } if (($jailed_user || $get_user_level eq '3') && !string_starts_with($path, $home)) { $path = $home . $path; $path =~ s/\/\//\//g; } } if ($nodir && -d $path) { print "$theme_text{'theme_xhred_global_error'}|-2"; } elsif (!-r $path) { print "$theme_text{'theme_xhred_global_error'}|-1"; } else { my $size = recursive_disk_usage($path); print nice_size($size, -1) . '|' . nice_number($size); } } elsif ($in{'xhr-get_list'} eq '1') { switch_to_remote_user_safe(); my $module = 'filemin'; # $in{'xhr-get_list_cmodule'}; exit if (!foreign_available($module)); my $path = "$in{'xhr-get_list_path'}"; my @dirs; my $jailed_user = get_fm_jailed_user($module); if ($jailed_user || $get_user_level eq '2' || $get_user_level eq '4' || webmin_user_is('safe-user')) { $path = ($jailed_user || get_user_home()) . $path; } opendir(my $dirs, $path); while (my $dir = readdir $dirs) { next unless -d $path . '/' . $dir; next if $dir eq '.' or $dir eq '..'; push @dirs, $dir; } closedir $dirs; @dirs = sort {"\L$a" cmp "\L$b"} @dirs; print convert_to_json(\@dirs); } elsif ($in{'xhr-encoding_convert'} eq '1') { my $module = 'filemin'; # $in{'xhr-encoding_convert_cmodule'}; exit if (!foreign_available($module)); my $jailed_user = get_fm_jailed_user($module, 1); my $jailed_user_home = get_fm_jailed_user($module); my $cfile = $in{'xhr-encoding_convert_file'}; if ($jailed_user) { switch_to_given_unix_user($jailed_user); $cfile = $jailed_user_home . $cfile; } else { switch_to_remote_user_safe(); } my $data = &ui_read_file_contents_limit( { 'file', $cfile, 'limit', $in{'xhr-encoding_convert_limit'}, 'reverse', $in{'xhr-encoding_convert_reverse'}, 'head', $in{'xhr-encoding_convert_head'}, 'tail', $in{'xhr-encoding_convert_tail'} }); if (-s $cfile < 128 || -T $cfile) { eval {$data = Encode::encode('utf-8', Encode::decode($in{'xhr-encoding_convert_name'}, $data));}; } print $data; } elsif ($in{'xhr-get_gpg_keys'} eq '1') { my $module = 'filemin'; # $in{'xhr-get_gpg_keys_cmodule'}; exit if (!foreign_available($module)); my $jailed_user = get_fm_jailed_user($module, 1); my ($public, $gpgpath) = get_user_allowed_gpg_keys($jailed_user, $in{'xhr-get_gpg_keys_all'}); my %keys; $keys{'public'} = $public; $keys{'gpgpath'} = $gpgpath; print convert_to_json(\%keys); } elsif ($in{'xhr-get_user_level'} eq '1') { print $get_user_level; } elsif ($in{'xhr-get_update_notice'} eq '1') { print update_notice(); } elsif ($in{'xhr-get_nice_size'} eq '1') { print nice_size($in{'xhr-get_nice_size_sum'}, -1); } elsif ($in{'xhr-get_command_exists'} eq '1') { print has_command($in{'xhr-get_command_exists_name'}); } elsif ($in{'xhr-theme_temp_data'} eq '1') { if ($in{'xhr-theme_temp_data_action'} eq 'set') { set_theme_temp_data($in{'xhr-theme_temp_data_name'}, $in{'xhr-theme_temp_data_value'}); } elsif ($in{'xhr-theme_temp_data_action'} eq 'get') { print get_theme_temp_data($in{'xhr-theme_temp_data_name'}, $in{'xhr-theme_temp_data_keep'}); } } elsif ($in{'xhr-shell-pop'}) { my $file = get_history_shell_file(); my $index = (int($in{'xhr-shell-pop'}) - 1); my $history = read_file_lines($file); if (@$history[$index]) { splice(@$history, $index, 1); flush_file_lines($file); print 1; } } elsif ($in{'xhr-shell-insert'}) { my $file = get_history_shell_file(); my $history = read_file_lines($file); push(@$history, $in{'xhr-shell-inserted'}) if ($in{'xhr-shell-inserted'}); flush_file_lines($file); print convert_to_json($history); } elsif ($in{'xhr-get_autocompletes'} eq '1') { if (foreign_available("shell")) { switch_to_remote_user_safe(); my @data = get_autocomplete_shell( $in{'xhr-get_autocomplete_type'}, $in{'xhr-get_autocomplete_string'}); print convert_to_json(\@data); } } elsif ($in{'xhr-theme_latest_version'} eq '1') { my @current_versions; my @remote_version = theme_remote_version(1, 0, 1); my ($remote_version_number) = "@remote_version" =~ /^version=(.*)/m; my ($remote_mversion_number) = "@remote_version" =~ /^mversion=(.*)/m; if ($remote_mversion_number <= 1) { $remote_mversion_number = ""; } else { $remote_mversion_number = "-$remote_mversion_number"; } my ($remote_bversion_number) = "@remote_version" =~ /^bversion=(.*)/m; if ($remote_bversion_number <= 1) { $remote_bversion_number = ""; } else { $remote_bversion_number = ":$remote_bversion_number"; } push(@current_versions, (theme_remote_version(1, 1) =~ /^version=(.*)/m), "$remote_version_number$remote_mversion_number$remote_bversion_number"); print convert_to_json(\@current_versions); } elsif ($in{'xhr-theme_clear_cache'} eq '1') { clear_theme_cache(&webmin_user_is_admin(), $in{'xhr-theme_clear_cache_full'}); } elsif ($in{'xhr-update'} eq '1' && &webmin_user_is_admin() && $theme_config{'settings_upgrade_allowed'} eq 'true') { my @update_rs; my $version_type = ($in{'xhr-update-type'} eq '-beta' ? '-beta' : '-release'); my $update_force = $in{'xhr-update-force'}; my $update_version = $in{'xhr-update-version'}; my $usermin_enabled_updates = ($theme_config{'settings_sysinfo_theme_updates_for_usermin'} ne 'false' ? 1 : 0); if (!has_command('git') || !has_command('curl') || !has_command('bash')) { @update_rs = { "no_git" => replace((!has_command('curl') || !has_command('bash') ? '>git<' : '~'), (!has_command('curl') ? '>curl<' : '>bash<'), $theme_text{'theme_git_patch_no_git_message'} ), }; print convert_to_json(\@update_rs); } else { if ($update_force ne "1" && !$update_version) { my $authentic_remote_data; if ($version_type eq '-release') { $authentic_remote_data = theme_remote_version(1, 1, undef, 1); } else { $authentic_remote_data = theme_remote_version(1, 0, 1, 1); } if ($authentic_remote_data eq '0') { @update_rs = { "no_connection" => $theme_text{'theme_git_update_locked'} }; print convert_to_json(\@update_rs); exit; } @update_rs = theme_update_incompatible($authentic_remote_data, ($version_type eq '-release' ? 1 : 0)); if (@update_rs) { print convert_to_json(\@update_rs); exit; } } my $usermin = ($has_usermin && $usermin_enabled_updates); my $usermin_root; $version_type = "$version_type:$update_version" if ($update_version); backquote_logged("yes | $root_directory/$current_theme/theme-update.sh $version_type -no-restart"); if ($usermin) { $usermin_root = $root_directory; $usermin_root =~ s/webmin/usermin/; backquote_logged("yes | $usermin_root/$current_theme/theme-update.sh $version_type -no-restart"); } my $tversion = theme_version('versionfull', 'no-cache'); @update_rs = { "success" => ($usermin ? theme_text('theme_git_patch_update_success_message2', $tversion) : theme_text('theme_git_patch_update_success_message', $tversion) ) }; print convert_to_json(\@update_rs); } } elsif ($in{'xhr-info'} eq '1') { if (&foreign_available('virtual-server')) { &foreign_require("virtual-server"); # Refresh regularly collected info on status of services &virtual_server::refresh_startstop_status(); } my @info = theme_list_combined_system_info(); our ($cpu_percent, $mem_percent, $virt_percent, $disk_percent, $host, $os, $webmin_version, $virtualmin_version, $cloudmin_version, $authentic_theme_version, $local_time, $kernel_arch, $cpu_type, $cpu_temperature, $cpu_fans, $hdd_temperature, $uptime, $running_proc, $load, $real_memory, $virtual_memory, $disk_space, $package_message, $csf_title, $csf_data, $csf_remote_version, $authentic_remote_version, $local_motd ) = get_sysinfo_vars(\@info); # Build update info my @updated_info = { "data" => 1, "cpu_percent" => $cpu_percent, "mem_percent" => $mem_percent, "virt_percent" => $virt_percent, "disk_percent" => $disk_percent, "host" => $host, "os" => $os, "webmin_version" => $webmin_version, "virtualmin_version" => $virtualmin_version, "cloudmin_version" => $cloudmin_version, "authentic_theme_version" => $authentic_theme_version, "local_time" => $local_time, "kernel_arch" => $kernel_arch, "cpu_type" => $cpu_type, "cpu_temperature" => $cpu_temperature, "cpu_fans" => $cpu_fans, "hdd_temperature" => $hdd_temperature, "uptime" => $uptime, "proc" => $running_proc, "cpu" => $load, "mem" => $real_memory, "virt" => $virtual_memory, "disk" => $disk_space, "package_message" => $package_message, "authentic_remote_version" => $authentic_remote_version, "local_motd" => $local_motd, "csf_title" => $csf_title, "csf_data" => $csf_data, "csf_remote_version" => $csf_remote_version, "csf_deny" => ( (defined(&csf_temporary_list) && $theme_config{'settings_sysinfo_csf_temp_list_privileged'} ne 'false') ? csf_temporary_list() : undef ), "collect_interval" => get_module_config_data('system-status', 'collect_interval'), "extended_si" => get_extended_sysinfo(\@info, undef), "warning_si" => get_sysinfo_warning(\@info), }; print convert_to_json(\@updated_info); } elsif ($in{'xhr-search-in-file'} eq '1') { switch_to_remote_user_safe(); my @files = split(/,/, $in{'xhr-search-in-file-files'}); my $match = trim($in{'xhr-search-in-file-string'}); my @match; fdo { my ($file, $line, $text) = @_; if ($text =~ /\Q$match\E/i) { push(@match, ([$files[$file] => [html_escape(substr($text, 0, 120)), $line]])); } } @files; print convert_to_json(\@match); } elsif ($in{'xhr-csf-unload'} eq '1') { lib_csf_control('unload'); } elsif ($in{'xhr-gennewpass'} eq 'get') { my $pass; if (&foreign_available('virtual-server')) { &foreign_require("virtual-server"); $pass = &virtual_server::random_password(); } elsif (&foreign_available('useradmin')) { &foreign_require("useradmin", "user-lib.pl"); $pass = &useradmin::generate_random_password(); } print $pass; } exit; } &$output(\%data); } 1;