Edit File: notify_expiring_certificates
#!/usr/local/cpanel/3rdparty/bin/perl # cpanel - scripts/notify_expiring_certificates Copyright 2017 cPanel, Inc. # All rights Reserved. # copyright@cpanel.net http://cpanel.net # This code is subject to the cpanel license. Unauthorized copying is prohibited package scripts::notify_expiring_certificates; use strict; use warnings; use Try::Tiny; use Cpanel::Time::ISO (); use Cpanel::LoadModule (); use Cpanel::Update::Recent (); use Cpanel::Apache::TLS::Index (); use Cpanel::Apache::TLS (); use Cpanel::SSL::Objects::Certificate::File (); use Cpanel::SSL::Auto::Providers (); use Cpanel::Transaction::File::JSON (); use Cpanel::Config::LoadCpConf (); use Cpanel::AcctUtils::DomainOwner::Tiny (); use Cpanel::WebVhosts::Owner (); use Try::Tiny; our $LAST_NOTIFY_RUN_FILE = '/var/cpanel/ssl/notify_expiring_certificates.json'; use constant DAY_IN_SECONDS => 86400; use constant AUTOSSL_NOTIFY_DAY_INTERVALS => ( 10, 5, 0, -1, -2, -3 ); use constant SSL_NOTIFY_DAY_INTERVALS => ( 30, 20, 10, 5, 0, -1, -2, -3 ); if ( !caller() ) { run(@ARGV); exit 0; } sub new { my ( $package, %opts ) = @_; return bless {}, $package; } sub run { my (@args) = @_; return 0 unless Cpanel::Config::LoadCpConf::loadcpconf_not_copy()->{'notify_expiring_certificates'}; return __PACKAGE__->new()->script(); } sub _time { return time(); } # for tests sub script { my ($self) = @_; Cpanel::AcctUtils::DomainOwner::Tiny::build_domain_cache() if !$Cpanel::AcctUtils::DomainOwner::Tiny::CACHE_IS_SET; my $atls_idx = Cpanel::Apache::TLS::Index->new(); my $all_records_ar = $atls_idx->get_all_ar(); my $now = _time(); # make sure all the notification checks are for the same time my $trans = Cpanel::Transaction::File::JSON->new( path => $LAST_NOTIFY_RUN_FILE ); my $last_run_data = $trans->get_data(); if ( ref $last_run_data ne 'HASH' ) { $trans->set_data( {} ); $last_run_data = $trans->get_data(); } $last_run_data->{'notify_history_by_uniq_key'} ||= {}; $last_run_data->{'last_notified_time'} = $now; my $notify_history_by_uniq_id_hr = $last_run_data->{'notify_history_by_uniq_key'}; _delete_notify_history_for_uniq_keys_that_no_longer_exist( $notify_history_by_uniq_id_hr, $all_records_ar ); _notify_and_remember_for_each_vhost_if_needed( $now, $notify_history_by_uniq_id_hr, $all_records_ar ); $trans->save_and_close_or_die(); return 1; } # We need a uniq key for each entry to track if # we have sent a notification for each interval # that we can delete when the cert or vhost # is changed/removed. sub _get_uniq_key_for_atls_entry { my ($entry) = @_; return join( '|', $entry->{'vhost_name'}, $entry->{'certificate_id'} ); } sub _delete_notify_history_for_uniq_keys_that_no_longer_exist { my ( $notify_history_by_uniq_id_hr, $all_records_ar ) = @_; my %current_uniq_keys = map { _get_uniq_key_for_atls_entry($_) => 1 } @$all_records_ar; my @uniq_keys_that_no_longer_exist = grep { !$current_uniq_keys{$_} } keys %{$notify_history_by_uniq_id_hr}; delete @{$notify_history_by_uniq_id_hr}{@uniq_keys_that_no_longer_exist}; return 1; } sub _notify_and_remember_for_each_vhost_if_needed { my ( $now, $notify_history_by_uniq_id_hr, $all_records_ar ) = @_; my $installed_autossl_providers = Cpanel::SSL::Auto::Providers->new(); my $upgraded_to_v68_in_the_last_ten_days = _upgraded_to_v68_in_the_last_ten_days($now); foreach my $crt (@$all_records_ar) { try { my $not_after_time_unix = Cpanel::Time::ISO::iso2unix( $crt->{'not_after'} ); my $cert_obj = Cpanel::SSL::Objects::Certificate::File->new( path => Cpanel::Apache::TLS->get_certificates_path( $crt->{'vhost_name'} ) ); my $auto_ssl_provider_obj = $installed_autossl_providers->get_provider_object_for_certificate_object($cert_obj); my $seconds_until_expired = $not_after_time_unix - $now; my $uniq_key = _get_uniq_key_for_atls_entry($crt); my $notify_history_hr = $notify_history_by_uniq_id_hr->{$uniq_key} ||= {}; my $notification_interval_to_send = _get_next_notification_interval_to_send( 'seconds_until_expired' => $seconds_until_expired, 'day_intervals' => [ $auto_ssl_provider_obj ? AUTOSSL_NOTIFY_DAY_INTERVALS : SSL_NOTIFY_DAY_INTERVALS ], # 'notify_history_hr' => $notify_history_hr ); if ( defined $notification_interval_to_send ) { # We do not send notifications for the first 10 days # since they won't have notification history and users # do not want a swarm of notifications catching up # for the ones they have "missed" since notifications # are new in v68. if ( !$upgraded_to_v68_in_the_last_ten_days ) { try { _notify_certificate_expiring( 'vhost_name' => $crt->{'vhost_name'}, 'user' => Cpanel::WebVhosts::Owner::get_webvhost_owner_or_die( $crt->{'vhost_name'} ), 'certificate_pem' => $cert_obj->text(), # AutoSSL::CertificateExpiring is a child class of SSL::CertificateExpiring # that will add the problems/queue info for the domain/vhost 'type' => ( $auto_ssl_provider_obj ? 'AutoSSL::CertificateExpiring' : 'SSL::CertificateExpiring' ) ); } catch { warn "Failed to send notification for $crt->{'vhost_name'}: $_"; }; } # Mark the notification interval as sent so we # do not send again for that interval $notify_history_hr->{$notification_interval_to_send} = $now; } } catch { warn "Failed to process “$crt->{'vhost_name'}”: $_"; }; } return; } sub _get_next_notification_interval_to_send { my (%opts) = @_; my ( $seconds_until_expired, $day_intervals_ar, $notify_history_hr ) = # @opts{ 'seconds_until_expired', 'day_intervals', 'notify_history_hr' }; my $first_notification_time = $day_intervals_ar->[0] * DAY_IN_SECONDS; return undef if $seconds_until_expired > $first_notification_time; foreach my $notify_time (@$day_intervals_ar) { next if $notify_history_hr->{$notify_time}; # Already did a notify for this interval my $notify_time_in_seconds = $notify_time * DAY_IN_SECONDS; # It doesn't matter which one as we only care if we need to. # Once we notify the next time will be set to later so we won't do it again until we trigger the next interval if ( $seconds_until_expired < $notify_time_in_seconds ) { return $notify_time; } } return undef; # No notification to send } sub _notify_certificate_expiring { my (%opts) = @_; my ( $type, $vhost_name, $user, $certificate_pem ) = # @opts{ 'type', 'vhost_name', 'user', 'certificate_pem' }; Cpanel::LoadModule::load_perl_module('Cpanel::Notify::Deferred') if !$INC{'Cpanel/Notify/Deferred.pm'}; Cpanel::LoadModule::load_perl_module('Cpanel::IP::Remote') if !$INC{'Cpanel/IP/Remote.pm'}; foreach my $target (qw(admin user)) { if ( $target eq 'user' ) { Cpanel::LoadModule::load_perl_module('Cpanel::ContactInfo') if !$INC{'Cpanel/ContactInfo.pm'}; my $cinfo = Cpanel::ContactInfo::get_contactinfo_for_user($user); next if $type eq 'AutoSSL::CertificateExpiring' && !$cinfo->{'notify_autossl_expiry'}; next if $type eq 'SSL::CertificateExpiring' && !$cinfo->{'notify_ssl_expiry'}; } #Have queueprocd do it so that we don’t create kazoodles of #notification processes which cripple lower-powered servers. Cpanel::Notify::Deferred::notify( 'class' => $type, 'application' => $type, 'constructor_args' => [ _get_icontact_args_for_target( $target, $user ), certificate_pem => $certificate_pem, vhost_name => $vhost_name, origin => 'notify_expiring_certificates', source_ip_address => Cpanel::IP::Remote::get_current_remote_ip(), ] ); } return; } sub _get_icontact_args_for_target { my ( $target, $user ) = @_; if ( $target eq 'user' ) { return ( user => $user, username => $user, to => $user, notification_targets_user_account => 1, ); } return ( username => $user, ); } sub _upgraded_to_v68_in_the_last_ten_days { return Cpanel::Update::Recent::upgraded_within_last_days(10); } 1;