Your IP : 216.73.216.209


Current Path : /proc/self/root/scripts/
Upload File :
Current File : //proc/self/root/scripts/shrink_modsec_ip_database

#!/usr/local/cpanel/3rdparty/bin/perl

# cpanel - scripts/shrink_modsec_ip_database       Copyright 2022 cPanel, L.L.C.
#                                                           All rights reserved.
# copyright@cpanel.net                                         http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited

package scripts::shrink_modsec_ip_database;

use strict;
use warnings;
use File::Temp                ();
use Cpanel::PwCache           ();
use Cpanel::FileUtils::Move   ();
use Cpanel::SafetyBits        ();
use Cpanel::SafetyBits::Chown ();
use Cpanel::AccessIds         ();
use Cpanel::SafeRun::Object   ();
use Cpanel::Imports;

our $MODSEC_SDBM_UTIL    = '/usr/sbin/modsec-sdbm-util';
our $DEFAULT_SECDATADIR  = '/var/cpanel/secdatadir';
our @DB_FILE_SUFFIXES    = qw( .pag .dir );                # Database file suffixes used by modsec-sdbm-util
our $NEW_DB_NAME         = 'new_db';                       # This name is hard-coded in modsec-sdbm-util
our $DB_PERMS            = 0640;                           # S_IRUSR | S_IWUSR | S_IRGRP
our $OTHER_EXECUTE_PERMS = 01;                             # S_IXOTH

sub new {
    my ( $pkg, $opts ) = @_;
    my $self = ref($opts) eq 'HASH' ? { %{$opts} } : {};
    bless $self, $pkg;
    return $self;
}

sub as_script {
    my $self = shift;
    logger->die('as_script() is a method call.') unless ref $self eq __PACKAGE__;

    if ( not $ARGV[0] or $ARGV[0] ne '-x' ) {
        my $msg = 'To execute, use the -x flag.';
        logger()->die($msg);
    }

    $self->run();

    return 1;
}

sub run {
    my $self = shift;
    logger->die('run() is a method call.') unless ref $self eq __PACKAGE__;

    return 0 unless $self->_bin_check;    # Bail out early and silently if the util is not installed

    my $databases = $self->_gather_databases();

    while ( my ( $db_path, $uid ) = each %{$databases} ) {
        if ( ( stat($MODSEC_SDBM_UTIL) )[2] & $OTHER_EXECUTE_PERMS ) {    # Can run util as "other" user?
            $self->_shrink_db_as_user( $uid, $db_path );
        }
        else {
            # Will have to settle for doing this as root.
            $self->_shrink_db( $uid, $db_path );
        }
    }
    return;
}

sub _bin_check {
    return -x $MODSEC_SDBM_UTIL ? 1 : 0;
}

sub _gather_databases {

    # All files that belong to the same database and that match @DB_FILE_SUFFIXES will need to have the same file owner or that database will not be in the final output
    my $self = shift;
    logger->die('_gather_databases() is a method call.') unless ref $self eq __PACKAGE__;

    return $self->{'databases'} if defined $self->{'databases'};

    my $secdatadir = $self->_secdatadir();

    my %databases;

    if ( opendir( my $dir_fh, $secdatadir ) ) {
      FILE: while ( my $filename = readdir($dir_fh) ) {
          SUFFIX: for my $suffix (@DB_FILE_SUFFIXES) {
                if ( $filename =~ m{ \A (.*) \Q$suffix\E \Z }xms ) {
                    my $short_name = $1;    # Filename without suffix

                    my $db_path = $secdatadir . '/' . $short_name;    # Database path name suitable for passing to modsec-sdbm-util
                    next FILE if exists $databases{$db_path};         # Move along if this belongs to a database already in the collection

                    my $owner = $self->_validate_database_files_owner($db_path);    # Check if there is a full set of files for this database path

                    if ( $self->_allowed_owner($owner) ) {
                        $databases{$db_path} = $owner;                              # Verified, add it to the collection
                        next FILE;
                    }

                }
            }
        }
        closedir($dir_fh);
    }
    return $self->{'databases'} = \%databases;
}

sub _shrink_db_as_user {
    my ( $self, $uid, $db_path ) = @_;
    logger->die('_shrink_db_as_user() is a method call.')                  unless ref $self eq __PACKAGE__;
    logger->die('_shrink_db_as_user() called without expected arguments.') unless length $uid && length $db_path;
    return Cpanel::AccessIds::do_as_user( $uid, sub { $self->_shrink_db( $uid, $db_path ) } );
}

sub _shrink_db {
    my ( $self, $uid, $db_path ) = @_;
    logger->die('_shrink_db() is a method call.')                  unless ref $self eq __PACKAGE__;
    logger->die('_shrink_db() called without expected arguments.') unless length $uid && length $db_path;

    my $secdatadir = $self->_secdatadir();

    my $workdir = File::Temp->newdir( CLEANUP => 1, TEMPLATE => 'shrink_modsec_db_XXXXXXXX', DIR => $secdatadir );
    Cpanel::SafetyBits::Chown::safe_chown_guess_gid( $uid, $workdir ) or logger->warn("Failed to chown $workdir to uid $uid");

    my @original_files = $self->_get_db_files($db_path);

    # modsec-sdbm-util will drop $NEW_DB_NAME * @DB_FILE_SUFFIXES files into $tempdir
    return 0 unless $self->_call_modsec_sdbm_util( $workdir, $db_path );

    # Verify new files exist and adjust perms
    my $new_db_path = $workdir . '/' . $NEW_DB_NAME;
    my @new_files   = map { $new_db_path . $_ } @DB_FILE_SUFFIXES;
    if ( !defined $self->_validate_database_files_owner($new_db_path) ) {    # root owned files = 0
        logger->warn("Failed to verify the database files generated by modsec-sdbm-util in the working directory");
        return 0;
    }
    $self->_set_default_perms( $uid, \@new_files );

    # Move the existing files to the workdir so we can revert if the new-file move fails
    my @revert_files = map { $workdir . '/original' . $_ } @DB_FILE_SUFFIXES;
    my $can_revert   = $self->_move_files( \@original_files, \@revert_files ) or logger->warn("Failed to move original files for $db_path into working dir");

    # Move new files into place
    if ( !$self->_move_files( \@new_files, \@original_files ) ) {
        logger->warn("Failed to move new files into place for $db_path");
        if ($can_revert) {
            $self->_move_files( \@revert_files, \@original_files ) or logger->warn("Failed to move backup files for $db_path from working dir to original location");
            $self->_set_default_perms( $uid, \@original_files );
        }
        else {
            logger->warn("Not able to restore original files for db_path");
        }
        return 0;
    }

    # Fix up final database permissions
    return 0 unless $self->_set_default_perms( $uid, \@original_files );

    return 1;
}

sub _call_modsec_sdbm_util {
    my ( $self, $tempdir, $db_path ) = @_;
    logger->die('_call_modsec_sdbm_util() is a method call.')                  unless ref $self eq __PACKAGE__;
    logger->die('_call_modsec_sdbm_util() called without expected arguments.') unless length $tempdir && length $db_path;

    my $run = Cpanel::SafeRun::Object->new(
        program => $MODSEC_SDBM_UTIL,
        args    => [ '-D', $tempdir, '-v', '-n', $db_path ],
    );

    # For whatever reason, if the util fails to open the specified db it doesn't exit with an error code, so parse out the error message.
    # It will fail to open if the file is immutable -- which is a crazy thing to do on purpose -- but it doesn't make that obvious.
    if ( $run->stdout() =~ m{ ^ Failed \s to \s open \s sdbm: \s (.*) $ }xms ) {
        logger()->warn("$MODSEC_SDBM_UTIL failed to open database (try checking all file/dir attributes): $1");
        return 0;
    }

    if ( $run->CHILD_ERROR() ) {
        logger()->warn( "$MODSEC_SDBM_UTIL exited with non-zero status: " . join( q{ }, map { $run->$_() // () } qw( autopsy stdout stderr ) ) );
        return 0;
    }

    return 1;
}

sub _validate_database_files_owner {

    # Expects a database path such as "$secdatadir/$db_name" without a suffix
    # Returns owner (uid) of a full set of database files if they exist, undef otherwise
    # Remember that root has uid 0!
    my ( $self, $db_path ) = @_;
    logger->die('_validate_database_files_owner() is a method call.')                  unless ref $self eq __PACKAGE__;
    logger->die('_validate_database_files_owner() called without expected arguments.') unless length $db_path;

    my $owner;
    for my $file ( $self->_get_db_files($db_path) ) {
        return unless -f $file;           # All generated filenames must exist
        my $seen = ( stat(_) )[4];
        $owner //= $seen;                 # Record owner of the first file we see
        return unless $owner == $seen;    # Validation fails if any file doesn't match recorded owner
    }
    return $owner;
}

sub _move_files {

    # Move a new set of files in place.  The indexes of the source and dest lists of files are expected to correlate directly for the rename.
    # For example, $source_files->[0] will be renamed to $dest_files->[0].
    my ( $self, $source_files, $dest_files ) = @_;
    logger->die('_move_files() is a method call.')                         unless ref $self eq __PACKAGE__;
    logger->die('_move_files() called without expected arguments.')        unless ref($source_files) eq 'ARRAY' && ref($dest_files) eq 'ARRAY';
    logger->die('_move_files() called without file lists of equal count.') unless scalar @$source_files == scalar @$dest_files;

    unlink @$dest_files;    # Though they would be overwritten by safemv, there's less chance for a mixture of old and new files if we remove all now and then something goes wrong later
    my $result = 1;
    while ( my ( $index, $source_file ) = each @$source_files ) {
        my $dest_file = $dest_files->[$index];
        if ( !Cpanel::FileUtils::Move::safemv( '-f', $source_file, $dest_file ) ) {
            logger->warn("Failed to move $source_file to $dest_file");
            $result = 0;    # Overall fail if any file doesn't move
        }
    }

    return $result;
}

sub _set_default_perms {
    my ( $self, $uid, $files ) = @_;
    logger->die('_set_default_perms() is a method call.')                  unless ref $self eq __PACKAGE__;
    logger->die('_set_default_perms() called without expected arguments.') unless length $uid && ref($files) eq 'ARRAY' && scalar @$files;
    for my $file (@$files) {
        if ( !-f $file ) {
            logger->warn("Missing expected file $file while trying to update permissions");
            return 0;    # Must bail out if all of the expected files don't exist.
        }

        Cpanel::SafetyBits::safe_chmod( $DB_PERMS, $uid, $file )       or logger->warn("Failed to chmod $file");
        Cpanel::SafetyBits::Chown::safe_chown_guess_gid( $uid, $file ) or logger->warn("Failed to chown $file to uid $uid");
    }
    return 1;
}

sub _get_db_files {

    # Expects a database path (i.e. "$secdatadir/$shortname") without a suffix
    # Generates list of files with known suffixes appended to database path (does not verify existence)
    my ( $self, $path ) = @_;
    logger->die('_get_db_files() is a method call.')                  unless ref $self eq __PACKAGE__;
    logger->die('_get_db_files() called without expected arguments.') unless length $path;

    return map { $path . $_ } @DB_FILE_SUFFIXES;
}

sub _allowed_owner {

    # If this is expanded to allow any user, ensure that $owner and its gid exists in Cpanel::PwCache to avoid death by Cpanel::SafetyBits::Chown::safe_chown_guess_gid
    my ( $self, $owner ) = @_;
    logger->die('_allowed_owner() is a method call.') unless ref $self eq __PACKAGE__;

    # undef $owner is not an implementation error here, it simply means the owner couldn't be determined or is intentionally being skipped.
    return unless defined $owner;

    my $nobody_uid = $self->{'nobody_uid'} //= ( Cpanel::PwCache::getpwnam('nobody') )[2];
    return unless defined $nobody_uid;

    return 1 if $owner == $nobody_uid;

    return 0;
}

sub _secdatadir {
    my $self = shift;
    logger->die('_secdatadir() is a method call.') unless ref $self eq __PACKAGE__;
    $self->{'secdatadir'} //= $DEFAULT_SECDATADIR;
    logger->die('Unable to determine secdatadir.') unless length $self->{'secdatadir'};
    return $self->{'secdatadir'};
}

if ( not caller() ) {
    my $shrink = scripts::shrink_modsec_ip_database->new();
    $shrink->as_script;
    exit 0;
}

1;

__END__

=head1 NAME

/scripts/shrink_modsec_ip_database

=head1 USAGE AS A SCRIPT

  /scripts/shrink_modsec_ip_database -x

=head2 AS A LIBRARY

This script is internally written as a modulino, which means it can be C<require>'d:

  use strict;
  require q{/scripts/shrink_modsec_ip_database};
  my $shrink = scripts::shrink_modsec_ip_database->new();
  $shrink->run();

=head1 REQUIRED ARGUMENTS

None

=head1 OPTIONS

=over 4

=item -x

Use this option to actually run the script, otherwise it will warn and return
without doing anything.

=back

=head1 DESCRIPTION

This script is called by C<scripts/maintenance>, and its purpose is to shrink
ModSecurity database files by removing expired entries.

=head1 DIAGNOSTICS

None

=head1 EXIT STATUS

Exit status is 0 (success) unless an unexpected error occurs.

=head1 DEPENDENCIES

This script relies on C</usr/sbin/modsec-sdbm-util> to be installed, and in order to be useful,
C<ModSecurity> must be installed and be enabled.

=head1 INCOMPATIBILITIES

None

=head1 BUGS AND LIMITATIONS

None

=head1 LICENSE AND COPYRIGHT

   Copyright 2022 cPanel, L.L.C.

Rosenblum TV: Video training, virtual workshops, classes, tutorials
logologologologo
  • About
  • What We Do
  • Our Clients
  • Case Studies
    • The BBC
    • CBS News
    • New York Times Television
    • Spectrum News
    • The Newark Star-Ledger
    • The United Nations
    • McGraw/Hill
    • Oyster Yachts
    • Scottish Environmental Protection Agency
  • The Power of Storytelling
  • Why iPhones?
  • Michael on Media
  • Books
  • Contact
  • About
  • What We Do
  • Our Clients
  • Case Studies
    • The BBC
    • CBS News
    • New York Times Television
    • Spectrum News
    • The Newark Star-Ledger
    • The United Nations
    • McGraw/Hill
    • Oyster Yachts
    • Scottish Environmental Protection Agency
  • The Power of Storytelling
  • Why iPhones?
  • Michael on Media
  • Books
  • Contact

RE-INVENTING THE TELEVISION NEWS BUSINESS*

A revolution in video storytelling

Creating entirely new & cost-effective production methods

From the world leaders in video production training and the creators of Character Driven Storytelling™

*and every other business that uses video

WHAT WE DO

Over the past 35 years, we have designed, built or restructured some of the most powerful news and journalism companies in the world.

We replace the traditional TV news ‘crew’ with one highly trained journalist, working alone with nothing but an iPhone.

No more TV news ‘crews’, no editors and no field producers.

This is television news done the way newspaper journalism is done – one reporter with their electronic pad and pencil.

In doing this, we can cut the cost of production by as much as 75% while increasing ratings and audience engagement.

In the place of conventional TV news ‘packages’ – ie, reporter stand up, interview, b-roll, man on the street, we marry great journalism with Netflix and Hollywood storytelling.

It’s a combination that works.

We have taken most of our clients to #1 in their respective markets.

And it’s not just for news. Any company, any profit, any NGO and anyone else who is online needs to tell their story in compelling yet cost-effective video. We can teach you to do that. Either in person or virtually.

EXAMPLES OF WHAT WE CAN TEACH YOUR STAFF TO PRODUCE

ITAY HOD

Itay Hod, MMJ with KPIX/CBS in San Francisco, took the 5-Day Intensive Video Storytelling Bootcamp in 2018.

Because he works alone, with only an iPhone, he was able to embed himself with a homeless family.

Here’s the story he produced in a one-day turn.

KIET DO

Kiet Do, an MMJ with KPIX/CBS in San Francisco, took the 5-Day Intensive Video Storytelling Bootcamp in 2021.

Here is a story he produced, all on his own, with only an iPhone and in a one-day turn.

TAYLOR SCHAUB

Taylor Schaub, an MMJ with Spectrum News 1 in LA, took the 5-Day Intensive Video Storytelling Bootcamp in 2023.

Here is a story he turned in only one day, using only an iPhone. It was the first video story he ever did and it was nominated for an Emmy.

THE BOOTCAMP

How do we convert stations and whole networks to working in this way?

Since 1988, we have run intensive 5-Day Video Storytelling Bootcamps

We have done these all over the world.

These are hands-on bootcamps, and participants learn an entirely new way of creating TV news stories.

-We shoot at a 3:1 ratio or lower, so turnaround times are fast.

-We go directly from camera to timelilne and edit – no written scripts.  We work in the medium of pictures and sound.

-We are entirely character-driven.

-We are driven by pictures and real events.

-We are focused almost entirely on ’the viewer experience’.

Since 1988, more than 70,000 journalists around the world have taken our bootcamps, either in person on virtualy.

Case Studies

CBS Case Study Logos
CBS News

We have started to work with CBS News, bringing our ideas of character-driven storytelling to one of the most successful and biggest networks in the United States. Since beginning to work with them ratings have climbed and more importantly, audience engagement is through the ceiling.

Learn More
NYT Case Study Logos
New York Times Television

We started New York Times Television in 1990 and it was the first paper to be brought into the world of TV. It quickly became one of the most successful non-fiction production companies in the United States. The series and documentaries we produced won many awards including multiple Emmys.

Learn More
BBC Case Study Logos
The BBC

We have been working with the BBC since the year 2000 helping to convert their national news network to our visual storytelling technique. Most recently we have trained teams from their sports, documentaries, and comedy divisions to make character-driven stories using only smartphones.

Learn More
Spectrum Case Study Logos
Spectrum News

For the past five years we have worked with Spectrum News to introduce and train their journalists on visual, character driven storytelling using smartphones helping to create a different kind of local news for their network of 24-Hour News Stations across the United States.

Learn More
UN Case Study Logos
The United Nations

In 2006, we were approached by the United Nations. Rather than rely on news outlets, it would be much easier to train the field operatives to produce their own stories. We spent two years working with the UN, training more than 100 of their staff in bootcamps in Geneva and Nairobi.

Learn More
Star Ledger Case Study Logos
The Newark Star-Ledger

We trained 50 print reporters at the paper to shoot and tell their own stories, in conjunction with their print work. We built a TV newsroom in their existing print newsroom – you could not ask for a better set and they began to live stream their stories in conjunction with their print work.

Learn More
Mcgraw Hill Case Study Logos
Mcgraw Hill

We spent two years with McGraw Hill, training more than 150 of their staffers, making them completely video literate. McGraw/Hill media properties we transit included Business Week, Aviation Week, (what was the name of the architecture magazine), and JD Power and Associates.

Learn More
VOA Case Study Logos
Voice of America

In 1990, we were approached by The Voice of America, the official broadcasting agency for the United States Government. When we met with VOA, they were only a short wave radio broadcaster, but working with them, we took them into television, launching VOA-TV.

Learn More
Oyster Case Study Logos
Oyster Yachts

British based Oyster Yachts makes some of the finest yachts in the world. Like every other company, they had to find a way to feed the never-ending video demands of social media – sites like Instagram and TikTok. We trained the Oyster staff to tell their own stories, using only iPhones.

Learn More
SEPA Case Study Logos
Scottish Environmental Protection Agency

We were approached by SEPA, the Scottish Environmental Protection Agency because they had to continually find a way to ‘feed the media beast’. The result was that SEPA was able to tell their own stories, whenever they wanted, and at almost no additional cost.

Learn More
Image 11-11-22 at 6.25 PM

Michael on Media

Michael Rosenblum has been writing about the media since 1988. His work and ideas have appeared in The Guardian, The Huffington Post, Ilkeston Life and many other publications.

He has been blogging regularly for the past 35 years on this subject. Having taught media studies at Columbia University, NYU and now the University of Oxford, he is considered an expert on this subject.

Continue reading this post or look back at previous posts.

Read More

Do You Have Questions About Learning Video Skills?

If you would like to know more about our courses contact us and one of our training advisors will be happy to call you.

Contact Us

Copyright 2024. All rights reserved.