From 4fe5186a914e6dd00c31c61c44c4026a12f4634a Mon Sep 17 00:00:00 2001 From: Ivan Kohler Date: Fri, 1 Jun 2012 19:44:53 -0700 Subject: [PATCH] remove autogenerated files entirely to avoid git merge conflicts --- rt/etc/upgrade/3.8-ical-extension | 96 --- rt/etc/upgrade/generate-rtaddressregexp | 87 --- rt/etc/upgrade/split-out-cf-categories | 171 ----- rt/etc/upgrade/vulnerable-passwords | 142 ---- rt/sbin/rt-attributes-viewer | 110 --- rt/sbin/rt-clean-sessions | 190 ----- rt/sbin/rt-dump-database | 199 ----- rt/sbin/rt-email-dashboards | 571 --------------- rt/sbin/rt-email-digest | 337 --------- rt/sbin/rt-email-group-admin | 508 ------------- rt/sbin/rt-server | 129 ---- rt/sbin/rt-shredder | 323 --------- rt/sbin/rt-validator | 1119 ----------------------------- rt/t/data/configs/apache2.2+fastcgi.conf | 44 -- rt/t/data/configs/apache2.2+mod_perl.conf | 41 -- 15 files changed, 4067 deletions(-) delete mode 100755 rt/etc/upgrade/3.8-ical-extension delete mode 100755 rt/etc/upgrade/generate-rtaddressregexp delete mode 100755 rt/etc/upgrade/split-out-cf-categories delete mode 100755 rt/etc/upgrade/vulnerable-passwords delete mode 100755 rt/sbin/rt-attributes-viewer delete mode 100755 rt/sbin/rt-clean-sessions delete mode 100755 rt/sbin/rt-dump-database delete mode 100755 rt/sbin/rt-email-dashboards delete mode 100755 rt/sbin/rt-email-digest delete mode 100755 rt/sbin/rt-email-group-admin delete mode 100755 rt/sbin/rt-server delete mode 100755 rt/sbin/rt-shredder delete mode 100755 rt/sbin/rt-validator delete mode 100644 rt/t/data/configs/apache2.2+fastcgi.conf delete mode 100644 rt/t/data/configs/apache2.2+mod_perl.conf diff --git a/rt/etc/upgrade/3.8-ical-extension b/rt/etc/upgrade/3.8-ical-extension deleted file mode 100755 index 7e3b87709..000000000 --- a/rt/etc/upgrade/3.8-ical-extension +++ /dev/null @@ -1,96 +0,0 @@ -#!/usr/bin/perl -# BEGIN BPS TAGGED BLOCK {{{ -# -# COPYRIGHT: -# -# This software is Copyright (c) 1996-2011 Best Practical Solutions, LLC -# -# -# (Except where explicitly superseded by other copyright notices) -# -# -# LICENSE: -# -# This work is made available to you under the terms of Version 2 of -# the GNU General Public License. A copy of that license should have -# been provided with this software, but in any event can be snarfed -# from www.gnu.org. -# -# This work is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA -# 02110-1301 or visit their web page on the internet at -# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html. -# -# -# CONTRIBUTION SUBMISSION POLICY: -# -# (The following paragraph is not intended to limit the rights granted -# to you to modify and distribute this software under the terms of -# the GNU General Public License and is only of importance to you if -# you choose to contribute your changes and enhancements to the -# community by submitting them to Best Practical Solutions, LLC.) -# -# By intentionally submitting any modifications, corrections or -# derivatives to this work, or any other work intended for use with -# Request Tracker, to Best Practical Solutions, LLC, you confirm that -# you are the copyright holder for those contributions and you grant -# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable, -# royalty-free, perpetual, license to use, copy, create derivative -# works based on those contributions, and sublicense and distribute -# those contributions and any derivatives thereof. -# -# END BPS TAGGED BLOCK }}} -use strict; -use warnings; - -use lib "/opt/rt3/local/lib"; -use lib "/opt/rt3/lib"; - - -use RT; -RT::LoadConfig(); -RT::Init(); - -use RT::Attributes; -my $attrs = RT::Attributes->new( $RT::SystemUser ); -$attrs->Limit(FIELD => 'ObjectType', OPERATOR=> '=', VALUE => 'RT::User'); -$attrs->Limit(FIELD => 'Name', OPERATOR=> '=', VALUE => 'ical-auth-token'); -while ( my $attr = $attrs->Next ) { - my $uid = $attr->ObjectId; - print "Processing auth token of user #". $uid ."...\n"; - - my $user = RT::User->new( $RT::SystemUser ); - $user->Load( $uid ); - unless ( $user->id ) { - print STDERR "\tERROR. Couldn't load user record\n"; - next; - } - - my ($status, $msg); - - ($status, $msg) = $user->DeleteAttribute('AuthToken') - if $user->FirstAttribute('AuthToken'); - unless ( $status ) { - print STDERR "\tERROR. Couldn't delete duplicated attribute: $msg\n"; - next; - } else { - print "\tdeleted duplicate attribute\n"; - } - - ($status, $msg) = $attr->SetName('AuthToken'); - unless ( $status ) { - print STDERR "\tERROR. Couldn't rename attribute: $msg\n"; - next; - } else { - print "\trenamed attribute\n"; - } - print "\tDONE\n"; -} - -exit 0; diff --git a/rt/etc/upgrade/generate-rtaddressregexp b/rt/etc/upgrade/generate-rtaddressregexp deleted file mode 100755 index cac08723b..000000000 --- a/rt/etc/upgrade/generate-rtaddressregexp +++ /dev/null @@ -1,87 +0,0 @@ -#!/usr/bin/perl -# BEGIN BPS TAGGED BLOCK {{{ -# -# COPYRIGHT: -# -# This software is Copyright (c) 1996-2011 Best Practical Solutions, LLC -# -# -# (Except where explicitly superseded by other copyright notices) -# -# -# LICENSE: -# -# This work is made available to you under the terms of Version 2 of -# the GNU General Public License. A copy of that license should have -# been provided with this software, but in any event can be snarfed -# from www.gnu.org. -# -# This work is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA -# 02110-1301 or visit their web page on the internet at -# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html. -# -# -# CONTRIBUTION SUBMISSION POLICY: -# -# (The following paragraph is not intended to limit the rights granted -# to you to modify and distribute this software under the terms of -# the GNU General Public License and is only of importance to you if -# you choose to contribute your changes and enhancements to the -# community by submitting them to Best Practical Solutions, LLC.) -# -# By intentionally submitting any modifications, corrections or -# derivatives to this work, or any other work intended for use with -# Request Tracker, to Best Practical Solutions, LLC, you confirm that -# you are the copyright holder for those contributions and you grant -# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable, -# royalty-free, perpetual, license to use, copy, create derivative -# works based on those contributions, and sublicense and distribute -# those contributions and any derivatives thereof. -# -# END BPS TAGGED BLOCK }}} -use strict; -use warnings; - -use lib "/opt/rt3/local/lib"; -use lib "/opt/rt3/lib"; - -use RT; -RT::LoadConfig(); -RT->Config->Set('LogToScreen' => 'debug'); -RT::Init(); - -$| = 1; - -if (my $re = RT->Config->Get('RTAddressRegexp')) { - print "No need to use this script, you already have RTAddressRegexp set to $re\n"; - exit; -} - -use RT::Queues; -my $queues = RT::Queues->new( $RT::SystemUser ); -$queues->UnLimit; -$queues->RowsPerPage(100); - -my @addresses = (RT->Config->Get('CorrespondAddress'), RT->Config->Get('CommentAddress')); -while ( my $queue = $queues->Next ) { - push @addresses, $queue->CorrespondAddress, $queue->CommentAddress; -} - -my %seen; -my $re = join '|', map "\Q$_\E", - grep defined && length && !$seen{ lc $_ }++, - @addresses; - -print < -# -# (Except where explicitly superseded by other copyright notices) -# -# -# LICENSE: -# -# This work is made available to you under the terms of Version 2 of -# the GNU General Public License. A copy of that license should have -# been provided with this software, but in any event can be snarfed -# from www.gnu.org. -# -# This work is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA -# 02110-1301 or visit their web page on the internet at -# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html. -# -# -# CONTRIBUTION SUBMISSION POLICY: -# -# (The following paragraph is not intended to limit the rights granted -# to you to modify and distribute this software under the terms of -# the GNU General Public License and is only of importance to you if -# you choose to contribute your changes and enhancements to the -# community by submitting them to Best Practical Solutions, LLC.) -# -# By intentionally submitting any modifications, corrections or -# derivatives to this work, or any other work intended for use with -# Request Tracker, to Best Practical Solutions, LLC, you confirm that -# you are the copyright holder for those contributions and you grant -# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable, -# royalty-free, perpetual, license to use, copy, create derivative -# works based on those contributions, and sublicense and distribute -# those contributions and any derivatives thereof. -# -# END BPS TAGGED BLOCK }}} -use strict; -use warnings; - -use lib "/opt/rt3/local/lib"; -use lib "/opt/rt3/lib"; - -use RT; -RT::LoadConfig(); -RT->Config->Set('LogToScreen' => 'debug'); -RT::Init(); - -$| = 1; - -$RT::Handle->BeginTransaction(); - -use RT::CustomFields; -my $CFs = RT::CustomFields->new( $RT::SystemUser ); -$CFs->UnLimit; -$CFs->Limit( FIELD => 'Type', VALUE => 'Select' ); - -my $seen; -while (my $cf = $CFs->Next ) { - next if $cf->BasedOnObj->Id; - my @categories; - my %mapping; - my $values = $cf->Values; - while (my $value = $values->Next) { - next unless defined $value->Category and length $value->Category; - push @categories, $value->Category unless grep {$_ eq $value->Category} @categories; - $mapping{$value->Name} = $value->Category; - } - next unless @categories; - - $seen++; - print "Found CF '@{[$cf->Name]}' with categories:\n"; - print " $_\n" for @categories; - - print "Split this CF's categories into a hierarchical custom field (Y/n)? "; - my $dothis = <>; - next if $dothis =~ /n/i; - - print "Enter name of CF to create as category ('@{[$cf->Name]} category'): "; - my $newname = <>; - chomp $newname; - $newname = $cf->Name . " category" unless length $newname; - - # bump the CF's sort oder up by one - $cf->SetSortOrder( ($cf->SortOrder || 0) + 1 ); - - # ..and add a new CF before it - my $new = RT::CustomField->new( $RT::SystemUser ); - my ($id, $msg) = $new->Create( - Name => $newname, - Type => 'Select', - MaxValues => 1, - LookupType => $cf->LookupType, - SortOrder => $cf->SortOrder - 1, - ); - die "Can't create custom field '$newname': $msg" unless $id; - - # Set the CF to be based on what we just made - $cf->SetBasedOn( $new->Id ); - - # Apply it to all of the same things - { - my $ocfs = RT::ObjectCustomFields->new( $RT::SystemUser ); - $ocfs->LimitToCustomField( $cf->Id ); - while (my $ocf = $ocfs->Next) { - my $newocf = RT::ObjectCustomField->new( $RT::SystemUser ); - ($id, $msg) = $newocf->Create( - SortOrder => $ocf->SortOrder, - CustomField => $new->Id, - ObjectId => $ocf->ObjectId, - ); - die "Can't create ObjectCustomField: $msg" unless $id; - } - } - - # Copy over all of the rights - { - my $acl = RT::ACL->new( $RT::SystemUser ); - $acl->LimitToObject( $cf ); - while (my $ace = $acl->Next) { - my $newace = RT::ACE->new( $RT::SystemUser ); - ($id, $msg) = $newace->Create( - PrincipalId => $ace->PrincipalId, - PrincipalType => $ace->PrincipalType, - RightName => $ace->RightName, - Object => $new, - ); - die "Can't assign rights: $msg" unless $id; - } - } - - # Add values for all of the categories - for my $i (0..$#categories) { - ($id, $msg) = $new->AddValue( - Name => $categories[$i], - SortOrder => $i + 1, - ); - die "Can't create custom field value: $msg" unless $id; - } - - # Grovel through all ObjectCustomFieldValues, and add the - # appropriate category - { - my $ocfvs = RT::ObjectCustomFieldValues->new( $RT::SystemUser ); - $ocfvs->LimitToCustomField( $cf->Id ); - while (my $ocfv = $ocfvs->Next) { - next unless exists $mapping{$ocfv->Content}; - my $newocfv = RT::ObjectCustomFieldValue->new( $RT::SystemUser ); - ($id, $msg) = $newocfv->Create( - CustomField => $new->Id, - ObjectType => $ocfv->ObjectType, - ObjectId => $ocfv->ObjectId, - Content => $mapping{$ocfv->Content}, - ); - } - } -} - -$RT::Handle->Commit; -print "No custom fields with categories found\n" unless $seen; diff --git a/rt/etc/upgrade/vulnerable-passwords b/rt/etc/upgrade/vulnerable-passwords deleted file mode 100755 index 0272e017b..000000000 --- a/rt/etc/upgrade/vulnerable-passwords +++ /dev/null @@ -1,142 +0,0 @@ -#!/usr/bin/perl -# BEGIN BPS TAGGED BLOCK {{{ -# -# COPYRIGHT: -# -# This software is Copyright (c) 1996-2011 Best Practical Solutions, LLC -# -# -# (Except where explicitly superseded by other copyright notices) -# -# -# LICENSE: -# -# This work is made available to you under the terms of Version 2 of -# the GNU General Public License. A copy of that license should have -# been provided with this software, but in any event can be snarfed -# from www.gnu.org. -# -# This work is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA -# 02110-1301 or visit their web page on the internet at -# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html. -# -# -# CONTRIBUTION SUBMISSION POLICY: -# -# (The following paragraph is not intended to limit the rights granted -# to you to modify and distribute this software under the terms of -# the GNU General Public License and is only of importance to you if -# you choose to contribute your changes and enhancements to the -# community by submitting them to Best Practical Solutions, LLC.) -# -# By intentionally submitting any modifications, corrections or -# derivatives to this work, or any other work intended for use with -# Request Tracker, to Best Practical Solutions, LLC, you confirm that -# you are the copyright holder for those contributions and you grant -# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable, -# royalty-free, perpetual, license to use, copy, create derivative -# works based on those contributions, and sublicense and distribute -# those contributions and any derivatives thereof. -# -# END BPS TAGGED BLOCK }}} -use strict; -use warnings; - -use lib "/opt/rt3/local/lib"; -use lib "/opt/rt3/lib"; - -use RT; -RT::LoadConfig; -RT::Init; - -$| = 1; - -use Getopt::Long; -use Digest::SHA; -my $fix; -GetOptions("fix!" => \$fix); - -use RT::Users; -my $users = RT::Users->new( $RT::SystemUser ); -$users->Limit( - FIELD => 'Password', - OPERATOR => 'IS NOT', - VALUE => 'NULL', - ENTRYAGGREGATOR => 'AND', -); -$users->Limit( - FIELD => 'Password', - OPERATOR => '!=', - VALUE => '*NO-PASSWORD*', - ENTRYAGGREGATOR => 'AND', -); -$users->Limit( - FIELD => 'Password', - OPERATOR => 'NOT STARTSWITH', - VALUE => '!', - ENTRYAGGREGATOR => 'AND', -); -push @{$users->{'restrictions'}{ "main.Password" }}, "AND", { - field => 'LENGTH(main.Password)', - op => '<', - value => '40', -}; - -# we want to update passwords on disabled users -$users->{'find_disabled_rows'} = 1; - -my $count = $users->Count; -if ($count == 0) { - print "No users with unsalted or weak cryptography found.\n"; - exit 0; -} - -if ($fix) { - print "Upgrading $count users...\n"; - while (my $u = $users->Next) { - my $stored = $u->__Value("Password"); - my $raw; - if (length $stored == 32) { - $raw = pack("H*",$stored); - } elsif (length $stored == 22) { - $raw = MIME::Base64::decode_base64($stored); - } elsif (length $stored == 13) { - printf "%20s => Old crypt() format, cannot upgrade\n", $u->Name; - } else { - printf "%20s => Unknown password format!\n", $u->Name; - } - next unless $raw; - - my $salt = pack("C4",map{int rand(256)} 1..4); - my $sha = Digest::SHA::sha256( - $salt . $raw - ); - $u->_Set( - Field => "Password", - Value => MIME::Base64::encode_base64( - $salt . substr($sha,0,26), ""), - ); - } - print "Done.\n"; - exit 0; -} else { - if ($count < 20) { - print "$count users found with unsalted or weak-cryptography passwords:\n"; - print " Id | Name\n", "-"x9, "+", "-"x9, "\n"; - while (my $u = $users->Next) { - printf "%8d | %s\n", $u->Id, $u->Name; - } - } else { - print "$count users found with unsalted or weak-cryptography passwords\n"; - } - - print "\n", "Run again with --fix to upgrade.\n"; - exit 1; -} diff --git a/rt/sbin/rt-attributes-viewer b/rt/sbin/rt-attributes-viewer deleted file mode 100755 index e141b83ae..000000000 --- a/rt/sbin/rt-attributes-viewer +++ /dev/null @@ -1,110 +0,0 @@ -#!/usr/bin/perl -# BEGIN BPS TAGGED BLOCK {{{ -# -# COPYRIGHT: -# -# This software is Copyright (c) 1996-2011 Best Practical Solutions, LLC -# -# -# (Except where explicitly superseded by other copyright notices) -# -# -# LICENSE: -# -# This work is made available to you under the terms of Version 2 of -# the GNU General Public License. A copy of that license should have -# been provided with this software, but in any event can be snarfed -# from www.gnu.org. -# -# This work is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA -# 02110-1301 or visit their web page on the internet at -# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html. -# -# -# CONTRIBUTION SUBMISSION POLICY: -# -# (The following paragraph is not intended to limit the rights granted -# to you to modify and distribute this software under the terms of -# the GNU General Public License and is only of importance to you if -# you choose to contribute your changes and enhancements to the -# community by submitting them to Best Practical Solutions, LLC.) -# -# By intentionally submitting any modifications, corrections or -# derivatives to this work, or any other work intended for use with -# Request Tracker, to Best Practical Solutions, LLC, you confirm that -# you are the copyright holder for those contributions and you grant -# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable, -# royalty-free, perpetual, license to use, copy, create derivative -# works based on those contributions, and sublicense and distribute -# those contributions and any derivatives thereof. -# -# END BPS TAGGED BLOCK }}} -use strict; -use warnings; - -# fix lib paths, some may be relative -BEGIN { - require File::Spec; - my @libs = ("/opt/rt3/lib", "/opt/rt3/local/lib"); - my $bin_path; - - for my $lib (@libs) { - unless ( File::Spec->file_name_is_absolute($lib) ) { - unless ($bin_path) { - if ( File::Spec->file_name_is_absolute(__FILE__) ) { - $bin_path = ( File::Spec->splitpath(__FILE__) )[1]; - } - else { - require FindBin; - no warnings "once"; - $bin_path = $FindBin::Bin; - } - } - $lib = File::Spec->catfile( $bin_path, File::Spec->updir, $lib ); - } - unshift @INC, $lib; - } -} - -my $id = shift; -usage() unless $id; - -sub usage { - print STDERR < - -Description: - -This script deserializes and print content of an attribute defined -by . May be useful for developers and for troubleshooting -problems. - -END - exit 1; -} - -require RT; -RT::LoadConfig(); -RT::Init(); - -require RT::Attribute; -my $attr = RT::Attribute->new( do { no warnings 'once'; $RT::SystemUser } ); -$attr->Load( $id ); -unless ( $attr->id ) { - print STDERR "Couldn't load attribute #$id\n"; - exit 1; -} - -my %res = (); -$res{$_} = $attr->$_() foreach qw(ObjectType ObjectId Name Description Content ContentType); - -use Data::Dumper; -print "Content of attribute #$id: ". Dumper( \%res ); - diff --git a/rt/sbin/rt-clean-sessions b/rt/sbin/rt-clean-sessions deleted file mode 100755 index 1649b3407..000000000 --- a/rt/sbin/rt-clean-sessions +++ /dev/null @@ -1,190 +0,0 @@ -#!/usr/bin/perl -# BEGIN BPS TAGGED BLOCK {{{ -# -# COPYRIGHT: -# -# This software is Copyright (c) 1996-2011 Best Practical Solutions, LLC -# -# -# (Except where explicitly superseded by other copyright notices) -# -# -# LICENSE: -# -# This work is made available to you under the terms of Version 2 of -# the GNU General Public License. A copy of that license should have -# been provided with this software, but in any event can be snarfed -# from www.gnu.org. -# -# This work is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA -# 02110-1301 or visit their web page on the internet at -# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html. -# -# -# CONTRIBUTION SUBMISSION POLICY: -# -# (The following paragraph is not intended to limit the rights granted -# to you to modify and distribute this software under the terms of -# the GNU General Public License and is only of importance to you if -# you choose to contribute your changes and enhancements to the -# community by submitting them to Best Practical Solutions, LLC.) -# -# By intentionally submitting any modifications, corrections or -# derivatives to this work, or any other work intended for use with -# Request Tracker, to Best Practical Solutions, LLC, you confirm that -# you are the copyright holder for those contributions and you grant -# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable, -# royalty-free, perpetual, license to use, copy, create derivative -# works based on those contributions, and sublicense and distribute -# those contributions and any derivatives thereof. -# -# END BPS TAGGED BLOCK }}} -use strict; -use warnings; - -# fix lib paths, some may be relative -BEGIN { - require File::Spec; - my @libs = ("/opt/rt3/lib", "/opt/rt3/local/lib"); - my $bin_path; - - for my $lib (@libs) { - unless ( File::Spec->file_name_is_absolute($lib) ) { - unless ($bin_path) { - if ( File::Spec->file_name_is_absolute(__FILE__) ) { - $bin_path = ( File::Spec->splitpath(__FILE__) )[1]; - } - else { - require FindBin; - no warnings "once"; - $bin_path = $FindBin::Bin; - } - } - $lib = File::Spec->catfile( $bin_path, File::Spec->updir, $lib ); - } - unshift @INC, $lib; - } -} - -use Getopt::Long; -my %opt; -GetOptions( \%opt, "older=s", "debug", "help", "skip-user"); - - -if ( $opt{help} ) { - require Pod::Usage; - import Pod::Usage; - pod2usage({ -message => "RT Session cleanup tool\n", verbose => 1 }); - exit 1; -} - - -if( $opt{'older'} ) { - unless( $opt{'older'} =~ /^\s*([0-9]+)\s*(H|D|M|Y)?$/i ) { - print STDERR "wrong format of the 'older' argumnet\n"; - exit(1); - } - my ($num,$unit) = ($1, uc($2 ||'D')); - my %factor = ( H => 60*60 ); - $factor{'D'} = $factor{'H'}*24; - $factor{'M'} = $factor{'D'}*31; - $factor{'Y'} = $factor{'D'}*365; - $opt{'older'} = $num * $factor{ $unit }; -} - -require RT; -RT::LoadConfig(); - -if( $opt{'debug'} ) { - RT->Config->Set( LogToScreen => 'debug' ); -} else { - RT->Config->Set( LogToScreen => undef ); -} - -RT::ConnectToDatabase(); -RT::InitLogging(); - -require RT::Interface::Web::Session; - -if( $opt{'older'} or my $alogoff = int RT->Config->Get('AutoLogoff') ) { - my $min; - foreach ($alogoff*60, $opt{'older'}) { - next unless $_; - $min = $_ unless $min; - $min = $_ if $_ < $min; - } - - RT::Interface::Web::Session->ClearOld( $min ); -} - -RT::Interface::Web::Session->ClearByUser - unless $opt{'skip-user'}; - -exit(0); - -__END__ - -=head1 NAME - -rt-clean-sessions - clean old and duplicate RT sessions - -=head1 SYNOPSIS - - rt-clean-sessions [--debug] [--older [H|D|M|Y]] - - rt-clean-sessions - rt-clean-sessions --debug - rt-clean-sessions --older 10D - rt-clean-sessions --debug --older 1M - rt-clean-sessions --older 10D --skip-user - -=head1 DESCRIPTION - -Script cleans RT sessions from DB or dir with sessions data. -Leaves in DB only one session per RT user and sessions that aren't older -than specified(see options). - -Script is safe because data in the sessions is temporary and can be deleted. - -=head1 OPTIONS - -=over 4 - -=item older - -Date interval in the C<< [] >> format. Default unit is D(ays), -H(our), M(onth) and Y(ear) are also supported. - -For example: C would delete all sessions that are -older than 1 month. - -=item skip-user - -By default only one session per user left in the DB, so users that have -sessions on multiple computers or in different browsers will be logged out. -Use this option to avoid this. - -=item debug - -Turn on debug output. - -=back - -=head1 NOTES - -Functionality similar to this is implemented in -html/Elements/SetupSessionCookie ; however, that does not guarantee -that a session will be removed from disk and database soon after the -timeout expires. This script, if run from a cron job, will ensure -that the timed out sessions are actually removed from disk; the Mason -component just ensures that the old sessions are not reusable before -the cron job gets to them. - -=cut diff --git a/rt/sbin/rt-dump-database b/rt/sbin/rt-dump-database deleted file mode 100755 index 2cdcc9279..000000000 --- a/rt/sbin/rt-dump-database +++ /dev/null @@ -1,199 +0,0 @@ -#!/usr/bin/perl -w -# BEGIN BPS TAGGED BLOCK {{{ -# -# COPYRIGHT: -# -# This software is Copyright (c) 1996-2011 Best Practical Solutions, LLC -# -# -# (Except where explicitly superseded by other copyright notices) -# -# -# LICENSE: -# -# This work is made available to you under the terms of Version 2 of -# the GNU General Public License. A copy of that license should have -# been provided with this software, but in any event can be snarfed -# from www.gnu.org. -# -# This work is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA -# 02110-1301 or visit their web page on the internet at -# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html. -# -# -# CONTRIBUTION SUBMISSION POLICY: -# -# (The following paragraph is not intended to limit the rights granted -# to you to modify and distribute this software under the terms of -# the GNU General Public License and is only of importance to you if -# you choose to contribute your changes and enhancements to the -# community by submitting them to Best Practical Solutions, LLC.) -# -# By intentionally submitting any modifications, corrections or -# derivatives to this work, or any other work intended for use with -# Request Tracker, to Best Practical Solutions, LLC, you confirm that -# you are the copyright holder for those contributions and you grant -# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable, -# royalty-free, perpetual, license to use, copy, create derivative -# works based on those contributions, and sublicense and distribute -# those contributions and any derivatives thereof. -# -# END BPS TAGGED BLOCK }}} -use strict; - -# As we specify that XML is UTF-8 and we output it to STDOUT, we must be sure -# it is UTF-8 so further XMLin will not break -binmode(STDOUT, ":utf8"); - -# fix lib paths, some may be relative -BEGIN { - require File::Spec; - my @libs = ("/opt/rt3/lib", "/opt/rt3/local/lib"); - my $bin_path; - - for my $lib (@libs) { - unless ( File::Spec->file_name_is_absolute($lib) ) { - unless ($bin_path) { - if ( File::Spec->file_name_is_absolute(__FILE__) ) { - $bin_path = ( File::Spec->splitpath(__FILE__) )[1]; - } - else { - require FindBin; - no warnings "once"; - $bin_path = $FindBin::Bin; - } - } - $lib = File::Spec->catfile( $bin_path, File::Spec->updir, $lib ); - } - unshift @INC, $lib; - } - -} - -use RT; -use XML::Simple; - -RT::LoadConfig(); -RT::Init(); - -my $LocalOnly = @ARGV ? shift(@ARGV) : 1; - -my %RV; -my %Ignore = ( - All => [qw( - id Created Creator LastUpdated LastUpdatedBy - )], - Templates => [qw( - TranslationOf - )], -); - -my $SystemUserId = $RT::SystemUser->Id; -my @classes = qw( - Users Groups Queues ScripActions ScripConditions - Templates Scrips ACL CustomFields -); -foreach my $class (@classes) { - require "RT/$class.pm"; - my $objects = "RT::$class"->new($RT::SystemUser); - $objects->{find_disabled_rows} = 1; - $objects->UnLimit; - - if ($class eq 'CustomFields') { - $objects->OrderByCols( - { FIELD => 'LookupType' }, - { FIELD => 'SortOrder' }, - { FIELD => 'Id' }, - ); - } - else { - $objects->OrderBy( FIELD => 'Id' ); - } - - if ($LocalOnly) { - next if $class eq 'ACL'; # XXX - would go into infinite loop - XXX - $objects->Limit( FIELD => 'LastUpdatedBy', OPERATOR => '!=', VALUE => $SystemUserId ) - unless $class eq 'Groups'; - $objects->Limit( FIELD => 'Id', OPERATOR => '!=', VALUE => $SystemUserId ) - if $class eq 'Users'; - $objects->Limit( FIELD => 'Domain', OPERATOR => '=', VALUE => 'UserDefined' ) - if $class eq 'Groups'; - } - - my %fields; - while (my $obj = $objects->Next) { - next if $obj->can('LastUpdatedBy') and $obj->LastUpdatedBy == $SystemUserId; - - if (!%fields) { - %fields = map { $_ => 1 } keys %{$obj->_ClassAccessible}; - delete @fields{ - @{$Ignore{$class}||=[]}, - @{$Ignore{All}||=[]}, - }; - } - - my $rv; - # next if $obj-> # skip default names - foreach my $field (sort keys %fields) { - my $value = $obj->__Value($field); - $rv->{$field} = $value if ( defined ($value) && length($value) ); - } - delete $rv->{Disabled} unless $rv->{Disabled}; - - foreach my $record (map { /ACL/ ? 'ACE' : substr($_, 0, -1) } @classes) { - foreach my $key (map "$record$_", ('', 'Id')) { - next unless exists $rv->{$key}; - my $id = $rv->{$key} or next; - my $obj = "RT::$record"->new($RT::SystemUser); - $obj->LoadByCols( Id => $id ) or next; - $rv->{$key} = $obj->__Value('Name') || 0; - } - } - - if ($class eq 'Users' and defined $obj->Privileged) { - $rv->{Privileged} = int($obj->Privileged); - } - elsif ($class eq 'CustomFields') { - my $values = $obj->Values; - while (my $value = $values->Next) { - push @{$rv->{Values}}, { - map { ($_ => $value->__Value($_)) } qw( - Name Description SortOrder - ), - }; - } - } - - if (eval { require RT::Attributes; 1 }) { - my $attributes = $obj->Attributes; - while (my $attribute = $attributes->Next) { - my $content = $attribute->Content; - $rv->{Attributes}{$attribute->Name} = $content if length($content); - } - } - - push @{$RV{$class}}, $rv; - } -} - -print(<< "."); -no strict; use XML::Simple; *_ = XMLin(do { local \$/; readline(DATA) }, ForceArray => [qw( - @classes Values -)], NoAttr => 1, SuppressEmpty => ''); *\$_ = (\$_{\$_} || []) for keys \%_; 1; # vim: ft=xml -__DATA__ -. - -print XMLout( - { map { ($_ => ($RV{$_} || [])) } @classes }, - RootName => 'InitialData', - NoAttr => 1, - SuppressEmpty => '', - XMLDecl => '', -); diff --git a/rt/sbin/rt-email-dashboards b/rt/sbin/rt-email-dashboards deleted file mode 100755 index 7cdb9c468..000000000 --- a/rt/sbin/rt-email-dashboards +++ /dev/null @@ -1,571 +0,0 @@ -#!/usr/bin/perl -# BEGIN BPS TAGGED BLOCK {{{ -# -# COPYRIGHT: -# -# This software is Copyright (c) 1996-2011 Best Practical Solutions, LLC -# -# -# (Except where explicitly superseded by other copyright notices) -# -# -# LICENSE: -# -# This work is made available to you under the terms of Version 2 of -# the GNU General Public License. A copy of that license should have -# been provided with this software, but in any event can be snarfed -# from www.gnu.org. -# -# This work is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA -# 02110-1301 or visit their web page on the internet at -# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html. -# -# -# CONTRIBUTION SUBMISSION POLICY: -# -# (The following paragraph is not intended to limit the rights granted -# to you to modify and distribute this software under the terms of -# the GNU General Public License and is only of importance to you if -# you choose to contribute your changes and enhancements to the -# community by submitting them to Best Practical Solutions, LLC.) -# -# By intentionally submitting any modifications, corrections or -# derivatives to this work, or any other work intended for use with -# Request Tracker, to Best Practical Solutions, LLC, you confirm that -# you are the copyright holder for those contributions and you grant -# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable, -# royalty-free, perpetual, license to use, copy, create derivative -# works based on those contributions, and sublicense and distribute -# those contributions and any derivatives thereof. -# -# END BPS TAGGED BLOCK }}} -use strict; -use warnings; - -# fix lib paths, some may be relative -BEGIN { - require File::Spec; - my @libs = ("/opt/rt3/lib", "/opt/rt3/local/lib"); - my $bin_path; - - for my $lib (@libs) { - unless ( File::Spec->file_name_is_absolute($lib) ) { - unless ($bin_path) { - if ( File::Spec->file_name_is_absolute(__FILE__) ) { - $bin_path = ( File::Spec->splitpath(__FILE__) )[1]; - } - else { - require FindBin; - no warnings "once"; - $bin_path = $FindBin::Bin; - } - } - $lib = File::Spec->catfile( $bin_path, File::Spec->updir, $lib ); - } - unshift @INC, $lib; - } - -} - -use RT; -use RT::Interface::CLI qw{ CleanEnv loc }; - -use Getopt::Long; -use HTML::Mason; -use HTML::RewriteAttributes::Resources; -use HTML::RewriteAttributes::Links; -use MIME::Types; -use POSIX 'tzset'; -use File::Temp 'tempdir'; - -# Clean out all the nasties from the environment -CleanEnv(); - -# Load the config file -RT::LoadConfig(); - -# Connect to the database and get RT::SystemUser and RT::Nobody loaded -RT::Init(); - -require RT::Interface::Web; -require RT::Interface::Web::Handler; -require RT::Dashboard; -$HTML::Mason::Commands::r = RT::Dashboard::FakeRequest->new; - -no warnings 'once'; - -# Read in the options -my %opts; -GetOptions( \%opts, - "help", "dryrun", "verbose", "debug", "epoch=i", "all", "skip-acl" -); - -if ($opts{'help'}) { - require Pod::Usage; - import Pod::Usage; - pod2usage(-message => "RT Email Dashboards\n", -verbose => 1); - exit 1; -} - -# helper functions -sub verbose { print loc(@_), "\n" if $opts{debug} || $opts{verbose}; 1 } -sub debug { print loc(@_), "\n" if $opts{debug}; 1 } -sub error { $RT::Logger->error(loc(@_)); verbose(@_); 1 } -sub warning { $RT::Logger->warning(loc(@_)); verbose(@_); 1 } - -my $now = $opts{epoch} || time; -verbose "Using time [_1]", scalar localtime($now); - -my $from = get_from(); -debug "Sending email from [_1]", $from; - -# look through each user for her subscriptions -my $Users = RT::Users->new($RT::SystemUser); -$Users->LimitToPrivileged; - -while (defined(my $user = $Users->Next)) { - if ($user->PrincipalObj->Disabled) { - debug "Skipping over " - . $user->Name - . " due to having a disabled account."; - next; - } - - my ($hour, $dow, $dom) = hour_dow_dom_in($user->Timezone || RT->Config->Get('Timezone')); - $hour .= ':00'; - debug "Checking [_1]'s subscriptions: hour [_2], dow [_3], dom [_4]", - $user->Name, $hour, $dow, $dom; - - my $currentuser = RT::CurrentUser->new; - $currentuser->LoadByName($user->Name); - - # look through this user's subscriptions, are any supposed to be generated - # right now? - for my $subscription ($user->Attributes->Named('Subscription')) { - my $counter = $subscription->SubValue('Counter') || 0; - - if (!$opts{all}) { - debug "Checking against subscription with frequency [_1], hour [_2], dow [_3], dom [_4]", - $subscription->SubValue('Frequency'), $subscription->SubValue('Hour'), - $subscription->SubValue('Dow'), $subscription->SubValue('Dom'); - - next if $subscription->SubValue('Frequency') eq 'never'; - - # correct hour? - next if $subscription->SubValue('Hour') ne $hour; - - # if weekly, correct day of week? - if ( $subscription->SubValue('Frequency') eq 'weekly' ) { - next if $subscription->SubValue('Dow') ne $dow; - my $fow = $subscription->SubValue('Fow') || 1; - if ( $counter % $fow ) { - $subscription->SetSubValues( Counter => $counter + 1 ) - unless $opts{'dryrun'}; - next; - } - } - - # if monthly, correct day of month? - elsif ($subscription->SubValue('Frequency') eq 'monthly') { - next if $subscription->SubValue('Dom') != $dom; - } - - elsif ($subscription->SubValue('Frequency') eq 'm-f') { - next if $dow eq 'Sunday' || $dow eq 'Saturday'; - } - } - - my $email = $subscription->SubValue('Recipient') - || $user->EmailAddress; - - eval { send_dashboard($currentuser, $email, $subscription) }; - if ( $@ ) { - error 'Caught exception: ' . $@; - } - else { - $subscription->SetSubValues( - Counter => $counter + 1 ) - unless $opts{'dryrun'}; - } - } -} - -sub send_dashboard { - my ($currentuser, $email, $subscription) = @_; - - my $rows = $subscription->SubValue('Rows'); - - my $dashboard = RT::Dashboard->new($currentuser); - - my ($ok, $msg) = $dashboard->LoadById($subscription->SubValue('DashboardId')); - - # failed to load dashboard. perhaps it was deleted or it changed privacy - if (!$ok) { - warning "Unable to load dashboard [_1] of subscription [_2] for user [_3]: [_4]", - $subscription->SubValue('DashboardId'), - $subscription->Id, - $currentuser->Name, - $msg; - - my $ok = RT::Interface::Email::SendEmailUsingTemplate( - From => $from, - To => $email, - Template => 'Error: Missing dashboard', - Arguments => { - SubscriptionObj => $subscription, - }, - ); - - # only delete the subscription if the email looks like it went through - if ($ok) { - my ($deleted, $msg) = $subscription->Delete(); - if ($deleted) { - verbose("Deleted an obsolete subscription: [_1]", $msg); - } - else { - warning("Unable to delete an obsolete subscription: [_1]", $msg); - } - } - else { - warning("Unable to notify [_1] of an obsolete subscription", $currentuser->Name); - } - - return; - } - - verbose 'Creating dashboard "[_1]" for user "[_2]":', - $dashboard->Name, - $currentuser->Name; - - if ($opts{'dryrun'}) { - print << "SUMMARY"; - Dashboard: @{[ $dashboard->Name ]} - User: @{[ $currentuser->Name ]} <$email> -SUMMARY - return; - } - - $HTML::Mason::Commands::session{CurrentUser} = $currentuser; - my $contents = run_component( - '/Dashboards/Render.html', - id => $dashboard->Id, - Preview => 0, - ); - - for (@{ RT->Config->Get('EmailDashboardRemove') || [] }) { - $contents =~ s/$_//g; - } - - debug "Got [_1] characters of output.", length $contents; - - $contents = HTML::RewriteAttributes::Links->rewrite( - $contents, - RT->Config->Get('WebURL') . '/Dashboards/Render.html', - ); - - email_dashboard($currentuser, $email, $dashboard, $subscription, $contents); -} - -sub email_dashboard { - my ($currentuser, $email, $dashboard, $subscription, $content) = @_; - - verbose 'Sending dashboard "[_1]" to user [_2] <[_3]>', - $dashboard->Name, - $currentuser->Name, - $email; - - my $subject = sprintf '[%s] ' . RT->Config->Get('DashboardSubject'), - RT->Config->Get('rtname'), - ucfirst($subscription->SubValue('Frequency')), - $dashboard->Name; - - my $entity = build_email($content, $from, $email, $subject); - - my $ok = RT::Interface::Email::SendEmail( - Entity => $entity, - ); - - debug "Done sending dashboard to [_1] <[_2]>", - $currentuser->Name, $email - and return if $ok; - - error 'Failed to email dashboard to user [_1] <[_2]>', - $currentuser->Name, $email; -} - -sub build_email { - my ($content, $from, $to, $subject) = @_; - my @parts; - my %cid_of; - - $content = HTML::RewriteAttributes::Resources->rewrite($content, sub { - my $uri = shift; - - # already attached this object - return "cid:$cid_of{$uri}" if $cid_of{$uri}; - - $cid_of{$uri} = time() . $$ . int(rand(1e6)); - my ($data, $filename, $mimetype, $encoding) = get_resource($uri); - - # downgrade non-text strings, because all strings are utf8 by - # default, which is wrong for non-text strings. - if ( $mimetype !~ m{text/} ) { - utf8::downgrade( $data, 1 ) or warning "downgrade $data failed"; - } - - push @parts, MIME::Entity->build( - Top => 0, - Data => $data, - Type => $mimetype, - Encoding => $encoding, - Disposition => 'inline', - Name => $filename, - 'Content-Id' => $cid_of{$uri}, - ); - - return "cid:$cid_of{$uri}"; - }, - inline_css => sub { - my $uri = shift; - my ($content) = get_resource($uri); - return $content; - }, - inline_imports => 1, - ); - - my $entity = MIME::Entity->build( - From => $from, - To => $to, - Subject => $subject, - Type => "multipart/mixed", - ); - - $entity->attach( - Data => Encode::encode_utf8($content), - Type => 'text/html', - Charset => 'UTF-8', - Disposition => 'inline', - ); - - for my $part (@parts) { - $entity->add_part($part); - } - - return $entity; -} - -sub get_from { - RT->Config->Get('DashboardAddress') || RT->Config->Get('OwnerEmail') -} - -{ - my $mason; - my $outbuf = ''; - my $data_dir = ''; - - sub mason { - unless ($mason) { - debug "Creating Mason object."; - - # user may not have permissions on the data directory, so create a - # new one - $data_dir = tempdir(CLEANUP => 1); - - $mason = HTML::Mason::Interp->new( - RT::Interface::Web::Handler->DefaultHandlerArgs, - out_method => \$outbuf, - autohandler_name => '', # disable forced login and more - data_dir => $data_dir, - ); - $mason->interp->set_escape( h => \&RT::Interface::Web::EscapeUTF8 ); - $mason->interp->set_escape( u => \&RT::Interface::Web::EscapeURI ); - $mason->interp->set_escape( j => \&RT::Interface::Web::EscapeJS ); - } - return $mason; - } - - sub run_component { - mason->exec(@_); - my $ret = $outbuf; - $outbuf = ''; - return $ret; - } -} - -{ - my %cache; - - sub hour_dow_dom_in { - my $tz = shift; - return @{$cache{$tz}} if exists $cache{$tz}; - - my ($hour, $dow, $dom); - - { - local $ENV{'TZ'} = $tz; - ## Using POSIX::tzset fixes a bug where the TZ environment variable - ## is cached. - tzset(); - (undef, undef, $hour, $dom, undef, undef, $dow) = localtime($now); - } - tzset(); # return back previous value - - $hour = "0$hour" - if length($hour) == 1; - $dow = (qw/Sunday Monday Tuesday Wednesday Thursday Friday Saturday/)[$dow]; - - return @{$cache{$tz}} = ($hour, $dow, $dom); - } -} - -sub get_resource { - my $uri = URI->new(shift); - my ($content, $filename, $mimetype, $encoding); - - verbose "Getting resource [_1]", $uri; - - # strip out the equivalent of WebURL, so we start at the correct / - my $path = $uri->path; - my $webpath = RT->Config->Get('WebPath'); - $path =~ s/^\Q$webpath//; - - # add a leading / if needed - $path = "/$path" - unless $path =~ m{^/}; - - # grab the query arguments - my %args; - for (split /&/, ($uri->query||'')) { - my ($k, $v) = /^(.*?)=(.*)$/ - or die "Unable to parse query parameter '$_'"; - - for ($k, $v) { s/%(..)/chr hex $1/ge } - - # no value yet, simple key=value - if (!exists $args{$k}) { - $args{$k} = $v; - } - # already have key=value, need to upgrade it to key=[value1, value2] - elsif (!ref($args{$k})) { - $args{$k} = [$args{$k}, $v]; - } - # already key=[value1, value2], just add the new value - else { - push @{ $args{$k} }, $v; - } - } - - debug "Running component '[_1]'", $path; - $content = run_component($path, %args); - - # guess at the filename from the component name - $filename = $1 if $path =~ m{^.*/(.*?)$}; - - # the rest of this was taken from Email::MIME::CreateHTML::Resolver::LWP - ($mimetype, $encoding) = MIME::Types::by_suffix($filename); - - my $content_type = $HTML::Mason::Commands::r->content_type; - if ($content_type) { - $mimetype = $content_type; - - # strip down to just a MIME type - $mimetype = $1 if $mimetype =~ /(\S+);\s*charset=(.*)$/; - } - - #If all else fails then some conservative and general-purpose defaults are: - $mimetype ||= 'application/octet-stream'; - $encoding ||= 'base64'; - - debug "Resource [_1]: length=[_2] filename='[_3]' mimetype='[_4]', encoding='[_5]'", - $uri, - length($content), - $filename, - $mimetype, - $encoding; - - return ($content, $filename, $mimetype, $encoding); -} - -package RT::Dashboard::FakeRequest; -sub new { bless {}, shift } -sub header_out { shift } -sub headers_out { shift } -sub content_type { - my $self = shift; - $self->{content_type} = shift if @_; - return $self->{content_type}; -} - -=head1 NAME - -rt-email-dashboards - Send email dashboards - -=head1 SYNOPSIS - - /opt/rt3/local/sbin/rt-email-dashboards [options] - -=head1 DESCRIPTION - -This tool will send users email based on how they have subscribed to -dashboards. A dashboard is a set of saved searches, the subscription controls -how often that dashboard is sent and how it's displayed. - -Each subscription has an hour, and possibly day of week or day of month. These -are taken to be in the user's timezone if available, UTC otherwise. - -=head1 SETUP - -You'll need to have cron run this script every hour. Here's an example crontab -entry to do this. - - 0 * * * * /usr/bin/perl /opt/rt3/local/sbin/rt-email-dashboards - -This will run the script every hour on the hour. This may need some further -tweaking to be run as the correct user. - -=head1 OPTIONS - -This tool supports a few options. Most are for debugging. - -=over 8 - -=item --help - -Display this documentation - -=item --dryrun - -Figure out which dashboards would be sent, but don't actually generate them - -=item --epoch SECONDS - -Instead of using the current time to figure out which dashboards should be -sent, use SECONDS (usually since midnight Jan 1st, 1970, so C<1192216018> would -be Oct 12 19:06:58 GMT 2007). - -=item --verbose - -Print out some tracing information (such as which dashboards are being -generated and sent out) - -=item --debug - -Print out more tracing information (such as each user and subscription that is -being considered) - -=item --all - -Ignore subscription frequency when considering each dashboard (should only be -used with --dryrun) - -=back - -=cut - diff --git a/rt/sbin/rt-email-digest b/rt/sbin/rt-email-digest deleted file mode 100755 index 466ac04c5..000000000 --- a/rt/sbin/rt-email-digest +++ /dev/null @@ -1,337 +0,0 @@ -#!/usr/bin/perl -# BEGIN BPS TAGGED BLOCK {{{ -# -# COPYRIGHT: -# -# This software is Copyright (c) 1996-2011 Best Practical Solutions, LLC -# -# -# (Except where explicitly superseded by other copyright notices) -# -# -# LICENSE: -# -# This work is made available to you under the terms of Version 2 of -# the GNU General Public License. A copy of that license should have -# been provided with this software, but in any event can be snarfed -# from www.gnu.org. -# -# This work is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA -# 02110-1301 or visit their web page on the internet at -# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html. -# -# -# CONTRIBUTION SUBMISSION POLICY: -# -# (The following paragraph is not intended to limit the rights granted -# to you to modify and distribute this software under the terms of -# the GNU General Public License and is only of importance to you if -# you choose to contribute your changes and enhancements to the -# community by submitting them to Best Practical Solutions, LLC.) -# -# By intentionally submitting any modifications, corrections or -# derivatives to this work, or any other work intended for use with -# Request Tracker, to Best Practical Solutions, LLC, you confirm that -# you are the copyright holder for those contributions and you grant -# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable, -# royalty-free, perpetual, license to use, copy, create derivative -# works based on those contributions, and sublicense and distribute -# those contributions and any derivatives thereof. -# -# END BPS TAGGED BLOCK }}} -use warnings; -use strict; - -BEGIN { - require File::Spec; - my @libs = ("/opt/rt3/lib", "/opt/rt3/local/lib"); - my $bin_path; - - for my $lib (@libs) { - unless ( File::Spec->file_name_is_absolute($lib) ) { - unless ($bin_path) { - if ( File::Spec->file_name_is_absolute(__FILE__) ) { - $bin_path = ( File::Spec->splitpath(__FILE__) )[1]; - } - else { - require FindBin; - no warnings "once"; - $bin_path = $FindBin::Bin; - } - } - $lib = File::Spec->catfile( $bin_path, File::Spec->updir, $lib ); - } - unshift @INC, $lib; - } - -} - -use Date::Format qw( strftime ); -use Getopt::Long; -use RT; -use RT::Interface::CLI qw( CleanEnv loc ); -use RT::Interface::Email; - -CleanEnv(); -RT::LoadConfig(); -RT::Init(); - -sub usage { - my ($error) = @_; - print loc("Usage: ") . "$0 -m (daily|weekly) [--print] [--help]\n"; - print loc( - "[_1] is a utility, meant to be run from cron, that dispatches all deferred RT notifications as a per-user digest.", - $0 - ) . "\n"; - print "\n\t-m, --mode\t" - . loc("Specify whether this is a daily or weekly run.") . "\n"; - print "\t-p, --print\t" - . loc("Print the resulting digest messages to STDOUT; don't mail them. Do not mark them as sent") - . "\n"; - print "\t-h, --help\t" . loc("Print this message") . "\n"; - - if ( $error eq 'help' ) { - exit 0; - } else { - print loc("Error") . ": " . loc($error) . "\n"; - exit 1; - } -} - -my ( $frequency, $print, $help ) = ( '', '', '' ); -GetOptions( - 'mode=s' => \$frequency, - 'print' => \$print, - 'help' => \$help, -); - -usage('help') if $help; -usage("Mode argument must be 'daily' or 'weekly'") - unless $frequency =~ /^(daily|weekly)$/; - -run( $frequency, $print ); - -sub run { - my $frequency = shift; - my $print = shift; - -## Find all the tickets that have been modified within the time frame -## described by $frequency. - - my ( $all_digest, $sent_transactions ) = find_transactions($frequency); - -## Iterate through our huge hash constructing the digest message -## for each user and sending it. - - foreach my $user ( keys %$all_digest ) { - my ( $contents_list, $contents_body ) = build_digest_for_user( $user, $all_digest->{$user} ); - # Now we have a content head and a content body. We can send a message. - if ( send_digest( $user, $contents_list, $contents_body ) ) { - print "Sent message to $user\n"; - mark_transactions_sent( $frequency, $user, values %{$sent_transactions->{$user}} ) unless ($print); - } else { - print "Failed to send message to $user\n"; - } - } -} -exit 0; - -# Subroutines. - -sub send_digest { - my ( $to, $index, $messages ) = @_; - - # Combine the index and the messages. - - my $body = "============== Tickets with activity in the last " - . ( $frequency eq 'daily' ? "day" : "seven days" ) . "\n\n"; - - $body .= $index; - $body .= "\n\n============== Messages recorded in the last " - . ( $frequency eq 'daily' ? "day" : "seven days" ) . "\n\n"; - $body .= $messages; - - # Load our template. If we cannot load the template, abort - # immediately rather than failing through many loops. - my $digest_template = RT::Template->new( RT->SystemUser ); - my ( $ret, $msg ) = $digest_template->Load('Email Digest'); - unless ($ret) { - print loc("Failed to load template") - . " 'Email Digest': " - . $msg - . ". Cannot continue.\n"; - exit 1; - } - ( $ret, $msg ) = $digest_template->Parse( Argument => $body ); - unless ($ret) { - print loc("Failed to parse template") - . " 'Email Digest'. Cannot continue.\n"; - exit 1; - } - - # Set our sender and recipient. - $digest_template->MIMEObj->head->replace( 'From', RT::Config->Get('CorrespondAddress') ); - $digest_template->MIMEObj->head->replace( 'To', $to ); - - if ($print) { - $digest_template->MIMEObj->print; - return 1; - } else { - return RT::Interface::Email::SendEmail( Entity => $digest_template->MIMEObj) - } -} - -=item mark_transactions_sent( $frequency, $user, @txn_list ); - -Takes a frequency string (either 'daily' or 'weekly'), a user and one or more -transaction objects as its arguments. Marks the given deferred -notifications as sent. - -=cut - -sub mark_transactions_sent { - my ( $freq, $user, @txns ) = @_; - return unless $freq =~ /(daily|weekly)/; - return unless @txns; - foreach my $txn (@txns) { - - # Grab the attribute, mark the "sent" as true, and store the new - # value. - if ( my $attr = $txn->FirstAttribute('DeferredRecipients') ) { - my $deferred = $attr->Content; - $deferred->{$freq}->{$user}->{'_sent'} = 1; - $txn->SetAttribute( - Name => 'DeferredRecipients', - Description => 'Deferred recipients for this message', - Content => $deferred, - ); - } - } -} - -sub since_date { - my $frequency = shift; - - # Specify a short time for digest overlap, in case we aren't starting - # this process exactly on time. - my $OVERLAP_HEDGE = -30; - - my $since_date = RT::Date->new( RT->SystemUser ); - $since_date->Set( Format => 'unix', Value => time() ); - if ( $frequency eq 'daily' ) { - $since_date->AddDays(-1); - } else { - $since_date->AddDays(-7); - } - - $since_date->AddSeconds($OVERLAP_HEDGE); - - return $since_date; -} - -sub find_transactions { - my $frequency = shift; - my $since_date = since_date($frequency); - - my $txns = RT::Transactions->new( RT->SystemUser ); - - # First limit to recent transactions. - $txns->Limit( - FIELD => 'Created', - OPERATOR => '>', - VALUE => $since_date->ISO - ); - - # Next limit to ticket transactions. - $txns->Limit( - FIELD => 'ObjectType', - OPERATOR => '=', - VALUE => 'RT::Ticket', - ENTRYAGGREGATOR => 'AND' - ); - my $all_digest = {}; - my $sent_transactions = {}; - - while ( my $txn = $txns->Next ) { - my $ticket = $txn->Ticket; - my $queue = $txn->TicketObj->QueueObj->Name; - # Xxx todo - may clobber if two queues have the same name - foreach my $user ( $txn->DeferredRecipients($frequency) ) { - $all_digest->{$user}->{$queue}->{$ticket}->{ $txn->id } = $txn->ContentObj; - $sent_transactions->{$user}->{ $txn->id } = $txn; - } - } - - return ( $all_digest, $sent_transactions ); -} - -sub build_digest_for_user { - my $user = shift; - my $user_digest = shift; - - my $contents_list = ''; # Holds the digest index. - my $contents_body = ''; # Holds the digest body. - - # Has the user been disabled since a message was deferred on his/her - # behalf? - my $user_obj = RT::User->new( RT->SystemUser ); - $user_obj->LoadByEmail($user); - if ( $user_obj->PrincipalObj->Disabled ) { - print STDERR loc("Skipping disabled user") . " $user\n"; - next; - } - - print loc("Message for user") . " $user:\n\n" if $print; - foreach my $queue ( keys %$user_digest ) { - $contents_list .= "Queue $queue:\n"; - $contents_body .= "Queue $queue:\n"; - foreach my $ticket ( sort keys %{ $user_digest->{$queue} } ) { - my $tkt_txns = $user_digest->{$queue}->{$ticket}; - my $ticket_obj = RT::Ticket->new( RT->SystemUser ); - $ticket_obj->Load($ticket); - - # Spit out the index entry for this ticket. - my $ticket_title = sprintf( - "#%d %s [%s]\t%s\n", - $ticket, $ticket_obj->Status, $ticket_obj->OwnerObj->Name, - $ticket_obj->Subject - ); - $contents_list .= $ticket_title; - - # Spit out the messages for the transactions on this ticket. - $contents_body .= "\n== $ticket_title\n"; - foreach my $txn ( sort keys %$tkt_txns ) { - my $msg = $tkt_txns->{$txn}; - - # $msg contains an RT::Attachment with our outgoing - # message. Print a few headers for clarity's sake. - $contents_body .= "From: " . $msg->GetHeader('From') . "\n"; - my $date = $msg->GetHeader('Date '); - unless ($date) { - my $txn_obj = RT::Transaction->new( RT->SystemUser ); - $txn_obj->Load($txn); - my $date_obj = RT::Date->new( RT->SystemUser ); - $date_obj->Set( - Format => 'sql', - Value => $txn_obj->Created - ); - $date = strftime( '%a, %d %b %Y %H:%M:%S %z', - @{ [ localtime( $date_obj->Unix ) ] } ); - } - $contents_body .= "Date: $date\n\n"; - $contents_body .= $msg->Content . "\n"; - $contents_body .= "-------\n"; - } # foreach transaction - } # foreach ticket - } # foreach queue - - return ( $contents_list, $contents_body ); - -} diff --git a/rt/sbin/rt-email-group-admin b/rt/sbin/rt-email-group-admin deleted file mode 100755 index 2025a24a6..000000000 --- a/rt/sbin/rt-email-group-admin +++ /dev/null @@ -1,508 +0,0 @@ -#!/usr/bin/perl -# BEGIN BPS TAGGED BLOCK {{{ -# -# COPYRIGHT: -# -# This software is Copyright (c) 1996-2011 Best Practical Solutions, LLC -# -# -# (Except where explicitly superseded by other copyright notices) -# -# -# LICENSE: -# -# This work is made available to you under the terms of Version 2 of -# the GNU General Public License. A copy of that license should have -# been provided with this software, but in any event can be snarfed -# from www.gnu.org. -# -# This work is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA -# 02110-1301 or visit their web page on the internet at -# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html. -# -# -# CONTRIBUTION SUBMISSION POLICY: -# -# (The following paragraph is not intended to limit the rights granted -# to you to modify and distribute this software under the terms of -# the GNU General Public License and is only of importance to you if -# you choose to contribute your changes and enhancements to the -# community by submitting them to Best Practical Solutions, LLC.) -# -# By intentionally submitting any modifications, corrections or -# derivatives to this work, or any other work intended for use with -# Request Tracker, to Best Practical Solutions, LLC, you confirm that -# you are the copyright holder for those contributions and you grant -# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable, -# royalty-free, perpetual, license to use, copy, create derivative -# works based on those contributions, and sublicense and distribute -# those contributions and any derivatives thereof. -# -# END BPS TAGGED BLOCK }}} -=head1 NAME - -rt-email-group-admin - Command line tool for administrating NotifyGroup actions - -=head1 SYNOPSIS - - rt-email-group-admin --list - rt-email-group-admin --create 'Notify foo team' --group Foo - rt-email-group-admin --create 'Notify foo team as comment' --comment --group Foo - rt-email-group-admin --create 'Notify group Foo and Bar' --group Foo --group Bar - rt-email-group-admin --create 'Notify user foo@bar.com' --user foo@bar.com - rt-email-group-admin --create 'Notify VIPs' --user vip1@bar.com - rt-email-group-admin --add 'Notify VIPs' --user vip2@bar.com --group vip1 --user vip3@foo.com - rt-email-group-admin --rename 'Notify VIPs' --newname 'Inform VIPs' - rt-email-group-admin --switch 'Notify VIPs' - rt-email-group-admin --delete 'Notify user foo@bar.com' - -=head1 DESCRIPTION - -This script list, create, modify or delete scrip actions in the RT DB. Once -you've created an action you can use it in a scrip. - -For example you can create the following action using this script: - - rt-email-group-admin --create 'Notify developers' --group 'Development Team' - -Then you can add the followoing scrip to your Bugs queue: - - Condition: On Create - Action: Notify developers - Template: Transaction - Stage: TransactionCreate - -Your development team will be notified on every new ticket in the queue. - -=cut - -use warnings; -use strict; - -# fix lib paths, some may be relative -BEGIN { - require File::Spec; - my @libs = ("/opt/rt3/lib", "/opt/rt3/local/lib"); - my $bin_path; - - for my $lib (@libs) { - unless ( File::Spec->file_name_is_absolute($lib) ) { - unless ($bin_path) { - if ( File::Spec->file_name_is_absolute(__FILE__) ) { - $bin_path = ( File::Spec->splitpath(__FILE__) )[1]; - } - else { - require FindBin; - no warnings "once"; - $bin_path = $FindBin::Bin; - } - } - $lib = File::Spec->catfile( $bin_path, File::Spec->updir, $lib ); - } - unshift @INC, $lib; - } - -} - -use RT; -RT::LoadConfig; -RT::Init; - -require RT::Principal; -require RT::User; -require RT::Group; -require RT::ScripActions; - -use Getopt::Long qw(GetOptions); - -our $cmd = 'usage'; -our $opts = {}; - -sub parse_args { - my $tmp; - Getopt::Long::Configure( "pass_through" ); - if ( GetOptions( 'list' => \$tmp ) && $tmp ) { - $cmd = 'list'; - } - elsif ( GetOptions( 'create=s' => \$tmp ) && $tmp ) { - $cmd = 'create'; - $opts->{'name'} = $tmp; - $opts->{'groups'} = []; - $opts->{'users'} = []; - GetOptions( 'comment' => \$opts->{'comment'} ); - GetOptions( 'group:s@' => $opts->{'groups'} ); - GetOptions( 'user:s@' => $opts->{'users'} ); - unless ( @{ $opts->{'users'} } + @{ $opts->{'groups'} } ) { - usage(); - exit(-1); - } - } - elsif ( GetOptions( 'add=s' => \$tmp ) && $tmp ) { - $cmd = 'add'; - $opts->{'name'} = $tmp; - $opts->{'groups'} = []; - $opts->{'users'} = []; - GetOptions( 'group:s@' => $opts->{'groups'} ); - GetOptions( 'user:s@' => $opts->{'users'} ); - unless ( @{ $opts->{'users'} } + @{ $opts->{'groups'} } ) { - usage(); - exit(-1); - } - } - elsif ( GetOptions( 'switch=s' => \$tmp ) && $tmp ) { - $cmd = 'switch'; - $opts->{'name'} = $tmp; - } - elsif ( GetOptions( 'rename=s' => \$tmp ) && $tmp ) { - $cmd = 'rename'; - $opts->{'name'} = $tmp; - GetOptions( 'newname=s' => \$opts->{'newname'} ); - unless ( $opts->{'newname'} ) { - usage(); - exit(-1); - } - } - elsif ( GetOptions( 'delete=s' => \$tmp ) && $tmp) { - $cmd = 'delete'; - $opts->{'name'} = $tmp; - } else { - $cmd = 'usage'; - } - - return; -} - -sub usage { - local $@; - eval "require Pod::PlainText;"; - if ( $@ ) { - print "see `perldoc $0`\n"; - } else { - my $parser = Pod::PlainText->new( sentence => 0, width => 78 ); - $parser->parse_from_file( $0 ); - } -} - -parse_args(); - -{ - eval "main::$cmd()"; - if ( $@ ) { - print STDERR $@ ."\n"; - } -} - -exit(0); - -=head1 USAGE - -rt-email-group-admin --COMMAND ARGS - -=head1 COMMANDS - -=head2 list - -Lists actions and its descriptions. - -=cut - -sub list { - my $actions = _get_our_actions(); - while( my $a = $actions->Next ) { - _list( $a ); - } - return; -} - -sub _list { - my $action = shift; - - print "Name: ". $action->Name() ."\n"; - print "Module: ". $action->ExecModule() ."\n"; - - my @princ = argument_to_list( $action ); - - print "Members: \n"; - foreach( @princ ) { - my $obj = RT::Principal->new( $RT::SystemUser ); - $obj->Load( $_ ); - next unless $obj->id; - - print "\t". $obj->PrincipalType; - print "\t=> ". $obj->Object->Name; - print "(Disabled!!!)" if $obj->Disabled; - print "\n"; - } - print "\n"; - return; -} - -=head2 create NAME [--comment] [--group GNAME] [--user UNAME] - -Creates new action with NAME and adds users and/or groups to its -recipient list. Would be notify as comment if --comment specified. - -=cut - -sub create { - my $actions = RT::ScripActions->new( $RT::SystemUser ); - $actions->Limit( - FIELD => 'Name', - VALUE => $opts->{'name'}, - ); - if ( $actions->Count ) { - print STDERR "ScripAction '". $opts->{'name'} ."' allready exists\n"; - exit(-1); - } - - my @groups = _check_groups( @{ $opts->{'groups'} } ); - my @users = _check_users( @{ $opts->{'users'} } ); - unless ( @users + @groups ) { - print STDERR "List of groups and users is empty\n"; - exit(-1); - } - - my $action = __create_empty( $opts->{'name'}, $opts->{'comment'} ); - - __add( $action, $_ ) foreach( @users ); - __add( $action, $_ ) foreach( @groups ); - - return; -} - -sub __create_empty { - my $name = shift; - my $as_comment = shift || 0; - require RT::ScripAction; - my $action = RT::ScripAction->new( $RT::SystemUser ); - $action->Create( - Name => $name, - Description => "Created with rt-email-group-admin script", - ExecModule => $as_comment? 'NotifyGroupAsComment': 'NotifyGroup', - Argument => '', - ); - - return $action; -} - -sub _check_groups -{ - return grep { $_ ? 1: do { print STDERR "Group '$_' skipped, doesn't exist\n"; 0; } } - map { __check_group($_) } @_; -} - -sub __check_group -{ - my $instance = shift; - require RT::Group; - my $obj = RT::Group->new( $RT::SystemUser ); - $obj->LoadUserDefinedGroup( $instance ); - return $obj->id ? $obj : undef; -} - -sub _check_users -{ - return grep { $_ ? 1: do { print STDERR "User '$_' skipped, doesn't exist\n"; 0; } } - map { __check_user($_) } @_; -} - -sub __check_user -{ - my $instance = shift; - require RT::User; - my $obj = RT::User->new( $RT::SystemUser ); - $obj->Load( $instance ); - return $obj->id ? $obj : undef; -} - -=head2 add NAME [--group GNAME] [--user UNAME] - -Adds groups and/or users to recipients of the action NAME. - -=cut - -sub add { - my $action = _get_action_by_name( $opts->{'name'} ); - unless ( $action ) { - print STDERR "ScripAction '". $opts->{'name'} ."' doesn't exist\n"; - exit(-1); - } - - my @groups = _check_groups( @{ $opts->{'groups'} } ); - my @users = _check_users( @{ $opts->{'users'} } ); - - unless ( @users + @groups ) { - print STDERR "List of groups and users is empty\n"; - exit(-1); - } - - __add( $action, $_ ) foreach @users; - __add( $action, $_ ) foreach @groups; - - return; -} - -sub __add -{ - my $action = shift; - my $obj = shift; - - my @cur = argument_to_list( $action ); - - my $id = $obj->id; - return if grep $_ == $id, @cur; - - push @cur, $id; - - return $action->__Set( Field => 'Argument', Value => join(',', @cur) ); -} - -=head2 delete NAME - -Deletes action NAME if scrips doesn't use it. - -=cut - -sub delete { - my $action = _get_action_by_name( $opts->{'name'} ); - unless ( $action ) { - print STDERR "ScripAction '". $opts->{'name'} ."' doesn't exist\n"; - exit(-1); - } - - require RT::Scrips; - my $scrips = RT::Scrips->new( $RT::SystemUser ); - $scrips->Limit( FIELD => 'ScripAction', VALUE => $action->id ); - if ( $scrips->Count ) { - my @sid; - while( my $s = $scrips->Next ) { - push @sid, $s->id; - } - print STDERR "ScripAction '". $opts->{'name'} ."'" - . " is in use by Scrip(s) ". join( ", ", map "#$_", @sid ) - . "\n"; - exit(-1); - } - - return __delete( $action ); -} - -sub __delete { - require DBIx::SearchBuilder::Record; - return DBIx::SearchBuilder::Record::Delete( shift ); -} - -sub _get_action_by_name { - my $name = shift; - my $actions = _get_our_actions(); - $actions->Limit( - FIELD => 'Name', - VALUE => $name - ); - - if ( $actions->Count > 1 ) { - print STDERR "More then one ScripAction with name '$name'\n"; - } - - return $actions->First; -} - -=head2 switch NAME - -Switch action NAME from notify as correspondence to comment and back. - -=cut - -sub switch { - my $action = _get_action_by_name( $opts->{'name'} ); - unless ( $action ) { - print STDERR "ScripAction '". $opts->{'name'} ."' doesn't exist\n"; - exit(-1); - } - - my %h = ( - NotifyGroup => 'NotifyGroupAsComment', - NotifyGroupAsComment => 'NotifyGroup' - ); - - return $action->__Set( - Field => 'ExecModule', - Value => $h{ $action->ExecModule } - ); -} - -=head2 rename NAME --newname NEWNAME - -Renames action NAME to NEWNAME. - -=cut - -sub rename { - my $action = _get_action_by_name( $opts->{'name'} ); - unless ( $action ) { - print STDERR "ScripAction '". $opts->{'name'} ."' doesn't exist\n"; - exit(-1); - } - - my $actions = RT::ScripActions->new( $RT::SystemUser ); - $actions->Limit( FIELD => 'Name', VALUE => $opts->{'newname'} ); - if ( $actions->Count ) { - print STDERR "ScripAction '". $opts->{'newname'} ."' allready exists\n"; - exit(-1); - } - - return $action->__Set( - Field => 'Name', - Value => $opts->{'newname'}, - ); -} - -=head2 NOTES - -If command has option --group or --user then you can use it more then once, -if other is not specified. - -=cut - -############### -#### Utils #### -############### - -sub argument_to_list { - my $action = shift; - require RT::Action::NotifyGroup; - return RT::Action::NotifyGroup->__SplitArg( $action->Argument ); -} - -sub _get_our_actions { - my $actions = RT::ScripActions->new( $RT::SystemUser ); - $actions->Limit( - FIELD => 'ExecModule', - VALUE => 'NotifyGroup', - ENTRYAGGREGATOR => 'OR', - ); - $actions->Limit( - FIELD => 'ExecModule', - VALUE => 'NotifyGroupAsComment', - ENTRYAGGREGATOR => 'OR', - ); - - return $actions; -} - -=head1 AUTHOR - -Ruslan U. Zakirov Eruz@bestpractical.comE - -=head1 SEE ALSO - -L, L - -=cut diff --git a/rt/sbin/rt-server b/rt/sbin/rt-server deleted file mode 100755 index 28370aee7..000000000 --- a/rt/sbin/rt-server +++ /dev/null @@ -1,129 +0,0 @@ -#!/usr/bin/perl -w -# BEGIN BPS TAGGED BLOCK {{{ -# -# COPYRIGHT: -# -# This software is Copyright (c) 1996-2011 Best Practical Solutions, LLC -# -# -# (Except where explicitly superseded by other copyright notices) -# -# -# LICENSE: -# -# This work is made available to you under the terms of Version 2 of -# the GNU General Public License. A copy of that license should have -# been provided with this software, but in any event can be snarfed -# from www.gnu.org. -# -# This work is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA -# 02110-1301 or visit their web page on the internet at -# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html. -# -# -# CONTRIBUTION SUBMISSION POLICY: -# -# (The following paragraph is not intended to limit the rights granted -# to you to modify and distribute this software under the terms of -# the GNU General Public License and is only of importance to you if -# you choose to contribute your changes and enhancements to the -# community by submitting them to Best Practical Solutions, LLC.) -# -# By intentionally submitting any modifications, corrections or -# derivatives to this work, or any other work intended for use with -# Request Tracker, to Best Practical Solutions, LLC, you confirm that -# you are the copyright holder for those contributions and you grant -# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable, -# royalty-free, perpetual, license to use, copy, create derivative -# works based on those contributions, and sublicense and distribute -# those contributions and any derivatives thereof. -# -# END BPS TAGGED BLOCK }}} -use warnings; -use strict; - -# fix lib paths, some may be relative -BEGIN { - require File::Spec; - my @libs = ("/opt/rt3/lib", "/opt/rt3/local/lib"); - my $bin_path; - - for my $lib (@libs) { - unless ( File::Spec->file_name_is_absolute($lib) ) { - unless ($bin_path) { - if ( File::Spec->file_name_is_absolute(__FILE__) ) { - $bin_path = ( File::Spec->splitpath(__FILE__) )[1]; - } - else { - require FindBin; - no warnings "once"; - $bin_path = $FindBin::Bin; - } - } - $lib = File::Spec->catfile( $bin_path, File::Spec->updir, $lib ); - } - unshift @INC, $lib; - } - -} - -use RT; -RT::LoadConfig(); -RT->InitLogging(); -if (RT->Config->Get('DevelMode')) { require Module::Refresh; } - -RT::CheckPerlRequirements(); -RT->InitPluginPaths(); - -my $port = shift @ARGV || RT->Config->Get('WebPort') || '8080'; - - -require RT::Handle; -my ($integrity, $state, $msg) = RT::Handle->CheckIntegrity; - -unless ( $integrity ) { - print STDERR <ConfigFile && !-w _) { - die "Since your configuration exists but is not writable, I'm refusing to do anything.\n"; - } - - RT->Config->Set( 'LexiconLanguages' => '*' ); - RT::I18N->Init; - - RT->InstallMode(1); -} else { - RT->ConnectToDatabase(); - RT->InitSystemObjects(); - RT->InitClasses(); - RT->InitPlugins(); -} - -require RT::Interface::Web::Standalone; -my $server = RT::Interface::Web::Standalone->new; -$server->net_server('RT::Interface::Web::Standalone::PreFork'); -$server->port($port); -$server->run(); - diff --git a/rt/sbin/rt-shredder b/rt/sbin/rt-shredder deleted file mode 100755 index 8cc0c0003..000000000 --- a/rt/sbin/rt-shredder +++ /dev/null @@ -1,323 +0,0 @@ -#!/usr/bin/perl -# BEGIN BPS TAGGED BLOCK {{{ -# -# COPYRIGHT: -# -# This software is Copyright (c) 1996-2011 Best Practical Solutions, LLC -# -# -# (Except where explicitly superseded by other copyright notices) -# -# -# LICENSE: -# -# This work is made available to you under the terms of Version 2 of -# the GNU General Public License. A copy of that license should have -# been provided with this software, but in any event can be snarfed -# from www.gnu.org. -# -# This work is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA -# 02110-1301 or visit their web page on the internet at -# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html. -# -# -# CONTRIBUTION SUBMISSION POLICY: -# -# (The following paragraph is not intended to limit the rights granted -# to you to modify and distribute this software under the terms of -# the GNU General Public License and is only of importance to you if -# you choose to contribute your changes and enhancements to the -# community by submitting them to Best Practical Solutions, LLC.) -# -# By intentionally submitting any modifications, corrections or -# derivatives to this work, or any other work intended for use with -# Request Tracker, to Best Practical Solutions, LLC, you confirm that -# you are the copyright holder for those contributions and you grant -# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable, -# royalty-free, perpetual, license to use, copy, create derivative -# works based on those contributions, and sublicense and distribute -# those contributions and any derivatives thereof. -# -# END BPS TAGGED BLOCK }}} -=head1 NAME - -rt-shredder - Script which wipe out tickets from RT DB - -=head1 SYNOPSIS - - rt-shredder --plugin list - rt-shredder --plugin help-Tickets - rt-shredder --plugin 'Tickets=query,Queue="general" and Status="deleted"' - - rt-shredder --sqldump unshred.sql --plugin ... - rt-shredder --force --plugin ... - -=head1 DESCRIPTION - -rt-shredder - is script that allow you to wipe out objects -from RT DB. This script uses API that L module adds to RT. -Script can be used as example of usage of the shredder API. - -=head1 USAGE - -You can use several options to control which objects script -should wipeout. - -=head1 OPTIONS - -=head2 --sqldump - -Outputs INSERT queiries into file. This dump can be used to restore data -after wiping out. - -By default creates files -F<< /var/data/RT-Shredder/-XXXX.sql >> - -=head2 --object (DEPRECATED) - -Option has been deprecated, use plugin C instead. - -=head2 --plugin '[=,[;,]...]' - -You can use plugins to select RT objects with various conditions. -See also --plugin list and --plugin help options. - -=head2 --plugin list - -Output list of the available plugins. - -=head2 --plugin help- - -Outputs help for specified plugin. - -=head2 --force - -Script doesn't ask any questions. - -=head1 SEE ALSO - -L - -=cut - -use strict; -use warnings FATAL => 'all'; - -# fix lib paths, some may be relative -BEGIN { - require File::Spec; - my @libs = ("/opt/rt3/lib", "/opt/rt3/local/lib"); - my $bin_path; - - for my $lib (@libs) { - unless ( File::Spec->file_name_is_absolute($lib) ) { - unless ($bin_path) { - if ( File::Spec->file_name_is_absolute(__FILE__) ) { - $bin_path = ( File::Spec->splitpath(__FILE__) )[1]; - } - else { - require FindBin; - no warnings "once"; - $bin_path = $FindBin::Bin; - } - } - $lib = File::Spec->catfile( $bin_path, File::Spec->updir, $lib ); - } - unshift @INC, $lib; - } - -} - -use RT::Shredder (); -use Getopt::Long qw(GetOptions); -use File::Spec (); - -use RT::Shredder::Plugin (); -# prefetch list of plugins -our %plugins = RT::Shredder::Plugin->List; - -our %opt; -parse_args(); - -RT::Shredder::Init( %opt ); -my $shredder = new RT::Shredder; - -{ - my $plugin = eval { $shredder->AddDumpPlugin( Arguments => { - file_name => $opt{'sqldump'}, - from_storage => 0, - } ) }; - if( $@ ) { - print STDERR "ERROR: Couldn't open SQL dump file: $@\n"; - exit 1 if $opt{'sqldump'}; - - print STDERR "WARNING: It's strongly recommended to use '--sqldump ' option\n"; - unless( $opt{'force'} ) { - exit 0 unless prompt_yN( "Do you want to proceed?" ); - } - } else { - print "SQL dump file is '". $plugin->FileName ."'\n"; - } -} - -my @objs = process_plugins( $shredder ); -prompt_delete_objs( \@objs ) unless $opt{'force'}; - -$shredder->PutObjects( Objects => $_ ) foreach @objs; -eval { $shredder->WipeoutAll }; -if( $@ ) { - require RT::Shredder::Exceptions; - if( my $e = RT::Shredder::Exception::Info->caught ) { - print "\nERROR: $e\n\n"; - exit 1; - } - die $@; -} - -sub prompt_delete_objs -{ - my( $objs ) = @_; - unless( @$objs ) { - print "Objects list is empty, try refine search options\n"; - exit 0; - } - my $list = "Next ". scalar( @$objs ) ." objects would be deleted:\n"; - foreach my $o( @$objs ) { - $list .= "\t". $o->_AsString ." object\n"; - } - print $list; - exit(0) unless prompt_yN( "Do you want to proceed?" ); -} - -sub prompt_yN -{ - my $text = shift; - print "$text [y/N] "; - unless( =~ /^(?:y|yes)$/i ) { - return 0; - } - return 1; -} - -sub usage -{ - require RT::Shredder::POD; - RT::Shredder::POD::shredder_cli( $0, \*STDOUT ); - exit 1; -} - -sub parse_args -{ - my $tmp; - Getopt::Long::Configure( "pass_through" ); - my @objs = (); - if( GetOptions( 'object=s' => \@objs ) && @objs ) { - print STDERR "Option --object had been deprecated, use plugin 'Objects' instead\n"; - exit(1); - } - - my @plugins = (); - if( GetOptions( 'plugin=s' => \@plugins ) && @plugins ) { - $opt{'plugin'} = \@plugins; - foreach my $str( @plugins ) { - if( $str =~ /^\s*list\s*$/ ) { - show_plugin_list(); - } elsif( $str =~ /^\s*help-(\w+)\s*$/ ) { - show_plugin_help( $1 ); - } elsif( $str =~ /^(\w+)(=.*)?$/ && !$plugins{$1} ) { - print "Couldn't find plugin '$1'\n"; - show_plugin_list(); - } - } - } - - # other options make no sense without previouse - usage() unless keys %opt; - - if( GetOptions( 'force' => \$tmp ) && $tmp ) { - $opt{'force'}++; - } - $tmp = undef; - if( GetOptions( 'sqldump=s' => \$tmp ) && $tmp ) { - $opt{'sqldump'} = $tmp; - } - return; -} - -sub process_plugins -{ - my $shredder = shift; - - my @res; - foreach my $str( @{ $opt{'plugin'} } ) { - my $plugin = new RT::Shredder::Plugin; - my( $status, $msg ) = $plugin->LoadByString( $str ); - unless( $status ) { - print STDERR "Couldn't load plugin\n"; - print STDERR "Error: $msg\n"; - exit(1); - } - if ( lc $plugin->Type eq 'search' ) { - push @res, _process_search_plugin( $shredder, $plugin ); - } - elsif ( lc $plugin->Type eq 'dump' ) { - _process_dump_plugin( $shredder, $plugin ); - } - } - return RT::Shredder->CastObjectsToRecords( Objects => \@res ); -} - -sub _process_search_plugin { - my ($shredder, $plugin) = @_; - my ($status, @objs) = $plugin->Run; - unless( $status ) { - print STDERR "Couldn't run plugin\n"; - print STDERR "Error: $objs[1]\n"; - exit(1); - } - - my $msg; - ($status, $msg) = $plugin->SetResolvers( Shredder => $shredder ); - unless( $status ) { - print STDERR "Couldn't set conflicts resolver\n"; - print STDERR "Error: $msg\n"; - exit(1); - } - return @objs; -} - -sub _process_dump_plugin { - my ($shredder, $plugin) = @_; - $shredder->AddDumpPlugin( - Object => $plugin, - ); -} - -sub show_plugin_list -{ - print "Plugins list:\n"; - print "\t$_\n" foreach( grep !/^Base$/, keys %plugins ); - exit(1); -} - -sub show_plugin_help -{ - my( $name ) = @_; - require RT::Shredder::POD; - unless( $plugins{ $name } ) { - print "Couldn't find plugin '$name'\n"; - show_plugin_list(); - } - RT::Shredder::POD::plugin_cli( $plugins{'Base'}, \*STDOUT, 1 ); - RT::Shredder::POD::plugin_cli( $plugins{ $name }, \*STDOUT ); - exit(1); -} - -exit(0); diff --git a/rt/sbin/rt-validator b/rt/sbin/rt-validator deleted file mode 100755 index 4bf84aff3..000000000 --- a/rt/sbin/rt-validator +++ /dev/null @@ -1,1119 +0,0 @@ -#!/usr/bin/perl -# BEGIN BPS TAGGED BLOCK {{{ -# -# COPYRIGHT: -# -# This software is Copyright (c) 1996-2011 Best Practical Solutions, LLC -# -# -# (Except where explicitly superseded by other copyright notices) -# -# -# LICENSE: -# -# This work is made available to you under the terms of Version 2 of -# the GNU General Public License. A copy of that license should have -# been provided with this software, but in any event can be snarfed -# from www.gnu.org. -# -# This work is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA -# 02110-1301 or visit their web page on the internet at -# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html. -# -# -# CONTRIBUTION SUBMISSION POLICY: -# -# (The following paragraph is not intended to limit the rights granted -# to you to modify and distribute this software under the terms of -# the GNU General Public License and is only of importance to you if -# you choose to contribute your changes and enhancements to the -# community by submitting them to Best Practical Solutions, LLC.) -# -# By intentionally submitting any modifications, corrections or -# derivatives to this work, or any other work intended for use with -# Request Tracker, to Best Practical Solutions, LLC, you confirm that -# you are the copyright holder for those contributions and you grant -# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable, -# royalty-free, perpetual, license to use, copy, create derivative -# works based on those contributions, and sublicense and distribute -# those contributions and any derivatives thereof. -# -# END BPS TAGGED BLOCK }}} -use strict; -use warnings; - -# fix lib paths, some may be relative -BEGIN { - require File::Spec; - my @libs = ("/opt/rt3/lib", "/opt/rt3/local/lib"); - my $bin_path; - - for my $lib (@libs) { - unless ( File::Spec->file_name_is_absolute($lib) ) { - unless ($bin_path) { - if ( File::Spec->file_name_is_absolute(__FILE__) ) { - $bin_path = ( File::Spec->splitpath(__FILE__) )[1]; - } - else { - require FindBin; - no warnings "once"; - $bin_path = $FindBin::Bin; - } - } - $lib = File::Spec->catfile( $bin_path, File::Spec->updir, $lib ); - } - unshift @INC, $lib; - } - -} - -use Getopt::Long; -my %opt = (); -GetOptions( - \%opt, - 'check|c', - 'resolve', - 'force', - 'verbose|v', -); - -usage() unless $opt{'check'}; -usage_warning() if $opt{'resolve'} && !$opt{'force'}; - -sub usage { - print STDERR <; -} - -use RT; -RT::LoadConfig(); -RT::Init(); - -my $dbh = $RT::Handle->dbh; -my $db_type = RT->Config->Get('DatabaseType'); - -my %TYPE = ( - 'Transactions.Field' => 'text', - 'Transactions.OldValue' => 'text', - 'Transactions.NewValue' => 'text', -); - -my @models = qw( - ACE - Attachment - Attribute - CachedGroupMember - CustomField - CustomFieldValue - GroupMember - Group - Link - ObjectCustomField - ObjectCustomFieldValue - Principal - Queue - ScripAction - ScripCondition - Scrip - Template - Ticket - Transaction - User -); - -my %redo_on; -$redo_on{'Delete'} = { - ACL => [], - - Attributes => [], - - Links => [], - - CustomFields => [], - CustomFieldValues => [], - ObjectCustomFields => [], - ObjectCustomFieldValues => [], - - Queues => [], - - Scrips => [], - ScripActions => [], - ScripConditions => [], - Templates => [], - - Tickets => [ 'Tickets -> other', 'Tickets <-> Role Groups' ], - Transactions => [ 'Attachments -> other' ], - - Principals => ['User <-> ACL equivalence group', 'GMs -> Groups, Members' ], - Users => ['User <-> ACL equivalence group', 'GMs -> Groups, Members', 'Principals -> Users' ], - Groups => ['User <-> ACL equivalence group', 'GMs -> Groups, Members', 'CGM vs. GM', 'Principals -> Groups' ], - - GroupMembers => [ 'CGM vs. GM' ], - CachedGroupMembers => [ 'CGM vs. GM' ], -}; -$redo_on{'Create'} = { - Principals => ['User <-> ACL equivalence group', 'GMs -> Groups, Members' ], - Groups => ['User <-> ACL equivalence group', 'GMs -> Groups, Members', 'CGM vs. GM' ], - GroupMembers => [ 'CGM vs. GM' ], - CachedGroupMembers => [ 'CGM vs. GM' ], -}; - -my %describe_cb; -%describe_cb = ( - Attachments => sub { - my $row = shift; - my $txn_id = $row->{transactionid}; - my $res = 'Attachment #'. $row->{id} .' -> Txn #'. $txn_id; - return $res .', '. describe( 'Transactions', $txn_id ); - }, - Transactions => sub { - my $row = shift; - return 'Transaction #'. $row->{id} .' -> object '. $row->{objecttype} .' #'. $row->{objectid}; - }, -); - -{ my %cache = (); -sub m2t($) { - my $model = shift; - return $cache{$model} if $cache{$model}; - my $class = "RT::$model"; - my $object = $class->new( $RT::SystemUser ); - return $cache{$model} = $object->Table; -} } - -my (@do_check, %redo_check); - -my @CHECKS; -foreach my $table ( qw(Users Groups) ) { - push @CHECKS, "$table -> Principals" => sub { - my $msg = "A record in $table refers not existing record in Principals." - ." The script can either create missing record in Principals" - ." or delete record in $table."; - my ($type) = ($table =~ /^(.*)s$/); - check_integrity( - $table, 'id' => 'Principals', 'id', - join_condition => 't.PrincipalType = ?', - bind_values => [ $type ], - action => sub { - my $id = shift; - return unless my $a = prompt_action( ['Delete', 'create'], $msg ); - - if ( $a eq 'd' ) { - delete_record( $table, $id ); - } - elsif ( $a eq 'c' ) { - my $principal_id = create_record( 'Principals', - id => $id, PrincipalType => $type, ObjectId => $id, Disabled => 0 - ); - } - else { - die "Unknown action '$a'"; - } - }, - ); - }; - - push @CHECKS, "Principals -> $table" => sub { - my $msg = "A record in Principals refers not existing record in $table." - ." In some cases it's possible to resurrect manually such records," - ." but this utility can only delete"; - - check_integrity( - 'Principals', 'id' => $table, 'id', - condition => 's.PrincipalType = ?', - bind_values => [ $table =~ /^(.*)s$/ ], - action => sub { - my $id = shift; - return unless prompt( 'Delete', $msg ); - - delete_record( 'Principals', $id ); - }, - ); - }; -} - -push @CHECKS, 'User <-> ACL equivalence group' => sub { - # from user to group - check_integrity( - 'Users', 'id' => 'Groups', 'Instance', - join_condition => 't.Domain = ? AND t.Type = ?', - bind_values => [ 'ACLEquivalence', 'UserEquiv' ], - action => sub { - my $id = shift; - return unless prompt( - 'Create', "Found an user that has no ACL equivalence group." - ); - - my $gid = create_record( 'Groups', - Domain => 'ACLEquivalence', Type => 'UserEquiv', Instance => $id, - ); - }, - ); - # from group to user - check_integrity( - 'Groups', 'Instance' => 'Users', 'id', - condition => 's.Domain = ? AND s.Type = ?', - bind_values => [ 'ACLEquivalence', 'UserEquiv' ], - action => sub { - my $id = shift; - return unless prompt( - 'Delete', "Found an user ACL equivalence group, but there is no user." - ); - - delete_record( 'Groups', $id ); - }, - ); - # one ACL equiv group for each user - check_uniqueness( - 'Groups', - columns => ['Instance'], - condition => '.Domain = ? AND .Type = ?', - bind_values => [ 'ACLEquivalence', 'UserEquiv' ], - ); -}; - -# check integrity of Queue role groups -push @CHECKS, 'Queues <-> Role Groups' => sub { - # XXX: we check only that there is at least one group for a queue - # from queue to group - check_integrity( - 'Queues', 'id' => 'Groups', 'Instance', - join_condition => 't.Domain = ?', - bind_values => [ 'RT::Queue-Role' ], - ); - # from group to queue - check_integrity( - 'Groups', 'Instance' => 'Queues', 'id', - condition => 's.Domain = ?', - bind_values => [ 'RT::Queue-Role' ], - action => sub { - my $id = shift; - return unless prompt( - 'Delete', "Found role group of not existant queue." - ); - - delete_record( 'Groups', $id ); - }, - ); -}; - -# check integrity of Ticket role groups -push @CHECKS, 'Tickets <-> Role Groups' => sub { - # XXX: we check only that there is at least one group for a queue - # from queue to group - check_integrity( - 'Tickets', 'id' => 'Groups', 'Instance', - join_condition => 't.Domain = ?', - bind_values => [ 'RT::Ticket-Role' ], - ); - # from group to ticket - check_integrity( - 'Groups', 'Instance' => 'Tickets', 'id', - condition => 's.Domain = ?', - bind_values => [ 'RT::Ticket-Role' ], - action => sub { - my $id = shift; - return unless prompt( - 'Delete', "Found a role group of not existant ticket." - ); - - delete_record( 'Groups', $id ); - }, - ); -}; - -# additional CHECKS on groups -push @CHECKS, 'Role Groups (Instance, Type) uniqueness' => sub { - # Check that Domain, Instance and Type are unique - check_uniqueness( - 'Groups', - columns => ['Domain', 'Instance', 'Type'], - condition => '.Domain LIKE ?', - bind_values => [ '%-Role' ], - ); -}; - - -push @CHECKS, 'GMs -> Groups, Members' => sub { - my $msg = "A record in GroupMembers references an object that doesn't exist." - ." May be you deleted a group or principal directly from DB?" - ." Usually it's ok to delete such records."; - check_integrity( - 'GroupMembers', 'GroupId' => 'Groups', 'id', - action => sub { - my $id = shift; - return unless prompt( 'Delete', $msg ); - - delete_record( 'GroupMembers', $id ); - }, - ); - check_integrity( - 'GroupMembers', 'MemberId' => 'Principals', 'id', - action => sub { - my $id = shift; - return unless prompt( 'Delete', $msg ); - - delete_record( 'GroupMembers', $id ); - }, - ); -}; - -# CGM and GM -push @CHECKS, 'CGM vs. GM' => sub { - # all GM record should be duplicated in CGM - check_integrity( - GroupMembers => ['GroupId', 'MemberId'], - CachedGroupMembers => ['GroupId', 'MemberId'], - join_condition => 't.ImmediateParentId = t.GroupId AND t.Via = t.id', - action => sub { - my $id = shift; - return unless prompt( - 'Create', - "Found a record in GroupMembers that has no direct duplicate in CachedGroupMembers table." - ); - - my $gm = RT::GroupMember->new( $RT::SystemUser ); - $gm->Load( $id ); - die "Couldn't load GM record #$id" unless $gm->id; - my $cgm = create_record( 'CachedGroupMembers', - GroupId => $gm->GroupId, MemberId => $gm->MemberId, - ImmediateParentId => $gm->GroupId, Via => undef, - Disabled => 0, # XXX: we should check integrity of Disabled field - ); - update_records( "CachedGroupMembers", { id => $cgm }, { Via => $cgm } ); - }, - ); - # all first level CGM records should have a GM record - check_integrity( - CachedGroupMembers => ['GroupId', 'MemberId'], - GroupMembers => ['GroupId', 'MemberId'], - condition => 's.ImmediateParentId = s.GroupId AND s.Via = s.id AND s.GroupId != s.MemberId', - action => sub { - my $id = shift; - return unless prompt( - 'Delete', - "Found a record in CachedGroupMembers for a (Group, Member) pair" - ." that doesn't exist in GroupMembers table." - ); - - delete_record( 'CachedGroupMembers', $id ); - }, - ); - # each group should have a CGM record where MemberId == GroupId - check_integrity( - Groups => ['id', 'id'], - CachedGroupMembers => ['GroupId', 'MemberId'], - join_condition => 't.ImmediateParentId = t.GroupId AND t.Via = t.id', - action => sub { - my $id = shift; - return unless prompt( - 'Create', - "Found a record in Groups that has no direct" - ." duplicate in CachedGroupMembers table." - ); - - my $g = RT::Group->new( $RT::SystemUser ); - $g->Load( $id ); - die "Couldn't load group #$id" unless $g->id; - die "Loaded group by $id has id ". $g->id unless $g->id == $id; - my $cgm = create_record( 'CachedGroupMembers', - GroupId => $id, MemberId => $id, - ImmediateParentId => $id, Via => undef, - Disabled => $g->Disabled, - ); - update_records( "CachedGroupMembers", { id => $cgm }, { Via => $cgm } ); - }, - ); - - # and back, each record in CGM with MemberId == GroupId without exceptions - # should reference a group - check_integrity( - CachedGroupMembers => ['GroupId', 'MemberId'], - Groups => ['id', 'id'], - condition => "s.GroupId = s.MemberId", - action => sub { - my $id = shift; - return unless prompt( - 'Delete', - "Found a record in CachedGroupMembers for a group that doesn't exist." - ); - - delete_record( 'CachedGroupMembers', $id ); - }, - ); - # Via - check_integrity( - CachedGroupMembers => 'Via', - CachedGroupMembers => 'id', - action => sub { - my $id = shift; - return unless prompt( - 'Delete', - "Found a record in CachedGroupMembers with Via referencing not existing record." - ); - - delete_record( 'CachedGroupMembers', $id ); - }, - ); - - # for every CGM where ImmediateParentId != GroupId there should be - # matching parent record (first level) - check_integrity( - CachedGroupMembers => ['ImmediateParentId', 'MemberId'], - CachedGroupMembers => ['GroupId', 'MemberId'], - join_condition => 't.Via = t.id', - condition => 's.ImmediateParentId != s.GroupId', - action => sub { - my $id = shift; - return unless prompt( - 'Delete', - "Found a record in CachedGroupMembers that referencing not existant record in CachedGroupMembers table." - ); - - delete_record( 'CachedGroupMembers', $id ); - }, - ); - - # for every CGM where ImmediateParentId != GroupId there should be - # matching "grand" parent record - check_integrity( - CachedGroupMembers => ['GroupId', 'ImmediateParentId', 'Via'], - CachedGroupMembers => ['GroupId', 'MemberId', 'id'], - condition => 's.ImmediateParentId != s.GroupId', - action => sub { - my $id = shift; - return unless prompt( - 'Delete', - "Found a record in CachedGroupMembers that referencing not existant record in CachedGroupMembers table." - ); - - delete_record( 'CachedGroupMembers', $id ); - }, - ); - - # CHECK recursive records: - # if we have CGM1 (G1,M1,V1,IP1) then for every GM2(G2, M2), where G2 == M1, - # we should have CGM3 where G3 = G1, M3 = M2, V3 = ID1, IP3 = M1 - { - my $query = <fetchrow_array ) { - print STDERR "Principal #$m is member of #$ip when #$ip is member of #$g,"; - print STDERR " but there is no cached GM record that $m is member of #$g.\n"; - $action->( - GroupId => $g, MemberId => $m, Via => $via, - ImmediateParentId => $ip, Disabled => $dis, - ); - } - } -}; - -# Tickets -push @CHECKS, 'Tickets -> other' => sub { - check_integrity( - 'Tickets', 'EffectiveId' => 'Tickets', 'id', - action => sub { - my $id = shift; - return unless prompt( - 'Delete', - "Found a ticket that's been merged into a ticket that don't exist anymore." - ); - - delete_record( 'Tickets', $id ); - }, - ); - check_integrity( - 'Tickets', 'Queue' => 'Queues', 'id', - ); - check_integrity( - 'Tickets', 'Owner' => 'Users', 'id', - ); - # XXX: check that owner is only member of owner role group -}; - - -push @CHECKS, 'Transactions -> other' => sub { - foreach my $model ( @models ) { - check_integrity( - 'Transactions', 'ObjectId' => m2t($model), 'id', - condition => 's.ObjectType = ?', - bind_values => [ "RT::$model" ], - action => sub { - my $id = shift; - return unless prompt( - 'Delete', "Found a transaction without object." - ); - - delete_record( 'Transactions', $id ); - }, - ); - } - # type = CustomField - check_integrity( - 'Transactions', 'Field' => 'CustomFields', 'id', - condition => 's.Type = ?', - bind_values => [ 'CustomField' ], - ); - # type = Take, Untake, Force, Steal or Give - check_integrity( - 'Transactions', 'OldValue' => 'Users', 'id', - condition => 's.Type IN (?, ?, ?, ?, ?)', - bind_values => [ qw(Take Untake Force Steal Give) ], - action => sub { - my $id = shift; - return unless prompt( - 'Delete', "Found a transaction regarding changes of Owner," - ." but User with id stored in OldValue column doesn't exist anymore." - ); - - delete_record( 'Transactions', $id ); - }, - ); - check_integrity( - 'Transactions', 'NewValue' => 'Users', 'id', - condition => 's.Type IN (?, ?, ?, ?, ?)', - bind_values => [ qw(Take Untake Force Steal Give) ], - action => sub { - my $id = shift; - return unless prompt( - 'Delete', "Found a transaction regarding changes of Owner," - ." but User with id stored in NewValue column doesn't exist anymore." - ); - - delete_record( 'Transactions', $id ); - }, - ); - # type = DelWatcher - check_integrity( - 'Transactions', 'OldValue' => 'Principals', 'id', - condition => 's.Type = ?', - bind_values => [ 'DelWatcher' ], - action => sub { - my $id = shift; - return unless prompt( - 'Delete', "Found a transaction describing watchers change," - ." but User with id stored in OldValue column doesn't exist anymore." - ); - - delete_record( 'Transactions', $id ); - }, - ); - # type = AddWatcher - check_integrity( - 'Transactions', 'NewValue' => 'Principals', 'id', - condition => 's.Type = ?', - bind_values => [ 'AddWatcher' ], - action => sub { - my $id = shift; - return unless prompt( - 'Delete', "Found a transaction describing watchers change," - ." but User with id stored in NewValue column doesn't exist anymore." - ); - - delete_record( 'Transactions', $id ); - }, - ); - -# XXX: Links need more love, uri is stored instead of id -# # type = DeleteLink -# check_integrity( -# 'Transactions', 'OldValue' => 'Links', 'id', -# condition => 's.Type = ?', -# bind_values => [ 'DeleteLink' ], -# ); -# # type = AddLink -# check_integrity( -# 'Transactions', 'NewValue' => 'Links', 'id', -# condition => 's.Type = ?', -# bind_values => [ 'AddLink' ], -# ); - - # type = Set, Field = Queue - check_integrity( - 'Transactions', 'NewValue' => 'Queues', 'id', - condition => 's.Type = ? AND s.Field = ?', - bind_values => [ 'Set', 'Queue' ], - action => sub { - my $id = shift; - return unless prompt( - 'Delete', "Found a transaction describing queue change," - ." but Queue with id stored in NewValue column doesn't exist anymore." - ); - - delete_record( 'Transactions', $id ); - }, - ); - check_integrity( - 'Transactions', 'OldValue' => 'Queues', 'id', - condition => 's.Type = ? AND s.Field = ?', - bind_values => [ 'Set', 'Queue' ], - action => sub { - my $id = shift; - return unless prompt( - 'Delete', "Found a transaction describing queue change," - ." but Queue with id stored in OldValue column doesn't exist anymore." - ); - - delete_record( 'Transactions', $id ); - }, - ); - # Reminders - check_integrity( - 'Transactions', 'NewValue' => 'Tickets', 'id', - join_condition => 't.Type = ?', - condition => 's.Type IN (?, ?, ?)', - bind_values => [ 'reminder', 'AddReminder', 'OpenReminder', 'ResolveReminder' ], - ); -}; - -# Attachments -push @CHECKS, 'Attachments -> other' => sub { - check_integrity( - Attachments => 'TransactionId', Transactions => 'id', - action => sub { - my $id = shift; - return unless prompt( - 'Delete', "Found an attachment without a transaction." - ); - delete_record( 'Attachments', $id ); - }, - ); - check_integrity( - Attachments => 'Parent', Attachments => 'id', - action => sub { - my $id = shift; - return unless prompt( - 'Delete', "Found an sub-attachment without its parent attachment." - ); - delete_record( 'Attachments', $id ); - }, - ); - check_integrity( - Attachments => 'Parent', - Attachments => 'id', - join_condition => 's.TransactionId = t.TransactionId', - ); -}; - -push @CHECKS, 'CustomFields and friends' => sub { - #XXX: ObjectCustomFields needs more love - check_integrity( - 'CustomFieldValues', 'CustomField' => 'CustomFields', 'id', - ); - check_integrity( - 'ObjectCustomFieldValues', 'CustomField' => 'CustomFields', 'id', - ); - foreach my $model ( @models ) { - check_integrity( - 'ObjectCustomFieldValues', 'ObjectId' => m2t($model), 'id', - condition => 's.ObjectType = ?', - bind_values => [ "RT::$model" ], - ); - } -}; - -push @CHECKS, Templates => sub { - check_integrity( - 'Templates', 'Queue' => 'Queues', 'id', - ); -}; - -push @CHECKS, Scrips => sub { - check_integrity( - 'Scrips', 'Queue' => 'Queues', 'id', - ); - check_integrity( - 'Scrips', 'ScripCondition' => 'ScripConditions', 'id', - ); - check_integrity( - 'Scrips', 'ScripAction' => 'ScripActions', 'id', - ); - check_integrity( - 'Scrips', 'Template' => 'Templates', 'id', - ); -}; - -push @CHECKS, Attributes => sub { - foreach my $model ( @models ) { - check_integrity( - 'Attributes', 'ObjectId' => m2t($model), 'id', - condition => 's.ObjectType = ?', - bind_values => [ "RT::$model" ], - ); - } -}; - -# Fix situations when Creator or LastUpdatedBy references ACL equivalence -# group of a user instead of user -push @CHECKS, 'FIX: LastUpdatedBy and Creator' => sub { - my %fix = (); - foreach my $model ( @models ) { - my $class = "RT::$model"; - my $object = $class->new( $RT::SystemUser ); - foreach my $column ( qw(LastUpdatedBy Creator) ) { - next unless $object->_Accessible( $column, 'auto' ); - - my $table = m2t($model); - my $query = <fetchrow_array ) { - print STDERR "Record #$rid in $table refers to ACL equivalence group #$gid of user #$uid"; - print STDERR " when must reference user.\n"; - $action->( $gid, $uid ); - if ( keys( %fix ) > 1000 ) { - $sth->finish; - last; - } - } - } - } - - if ( keys %fix ) { - foreach my $table ( keys %fix ) { - foreach my $column ( keys %{ $fix{ $table } } ) { - my $query = "UPDATE $table SET $column = ? WHERE $column = ?"; - while ( my ($gid, $uid) = each %{ $fix{ $table }{ $column } } ) { - update_records( $table, { $column => $gid }, { $column => $uid } ); - } - } - } - $redo_check{'FIX: LastUpdatedBy and Creator'} = 1; - } -}; - -push @CHECKS, 'LastUpdatedBy and Creator' => sub { - foreach my $model ( @models ) { - my $class = "RT::$model"; - my $object = $class->new( $RT::SystemUser ); - my $table = $object->Table; - foreach my $column ( qw(LastUpdatedBy Creator) ) { - next unless $object->_Accessible( $column, 'auto' ); - check_integrity( - $table, $column => 'Users', 'id', - action => sub { - my ($id, %prop) = @_; - return unless my $replace_with = prompt_integer( - 'Replace', - "Column $column should point to a user, but there is record #$id in table $table\n" - ."where it's not true. It's ok to replace these wrong references with id of any user.\n" - ."Note that id you enter is not checked. You can peak any user from your DB, but it's\n" - ."may be better to create a special user for this, for example 'user_that_has_been_deleted'\n" - ."or something like that.", - "$table.$column -> user #$prop{$column}" - ); - update_records( $table, { id => $id, $column => $prop{$column} }, { $column => $replace_with } ); - }, - ); - } - } -}; -my %CHECKS = @CHECKS; - -@do_check = do { my $i = 1; grep $i++%2, @CHECKS }; - -while ( my $check = shift @do_check ) { - $CHECKS{ $check }->(); - - foreach my $redo ( keys %redo_check ) { - die "check $redo doesn't exist" unless $CHECKS{ $redo }; - delete $redo_check{ $redo }; - next if grep $_ eq $redo, @do_check; # don't do twice - push @do_check, $redo; - } -} - -sub check_integrity { - my ($stable, @scols) = (shift, shift); - my ($ttable, @tcols) = (shift, shift); - my %args = @_; - - @scols = @{ $scols[0] } if ref $scols[0]; - @tcols = @{ $tcols[0] } if ref $tcols[0]; - - print "Checking integrity of $stable.{". join(', ', @scols) ."} => $ttable.{". join(', ', @tcols) ."}\n" - if $opt{'verbose'}; - - my $query = "SELECT s.id, ". join(', ', map "s.$_", @scols) - ." FROM $stable s LEFT JOIN $ttable t" - ." ON (". join( - ' AND ', map columns_eq_cond('s', $stable, $scols[$_] => 't', $ttable, $tcols[$_]), (0..(@scols-1)) - ) .")" - . ($args{'join_condition'}? " AND ( $args{'join_condition'} )": "") - ." WHERE t.id IS NULL" - ." AND ". join(' AND ', map "s.$_ IS NOT NULL", @scols); - - $query .= " AND ( $args{'condition'} )" if $args{'condition'}; - - my @binds = @{ $args{'bind_values'} || [] }; - if ( $tcols[0] eq 'id' && @tcols == 1 ) { - my $type = $TYPE{"$stable.$scols[0]"} || 'number'; - if ( $type eq 'number' ) { - $query .= " AND s.$scols[0] != ?" - } - elsif ( $type eq 'text' ) { - $query .= " AND s.$scols[0] NOT LIKE ?" - } - push @binds, 0; - } - - my $sth = execute_query( $query, @binds ); - while ( my ($sid, @set) = $sth->fetchrow_array ) { - print STDERR "Record #$sid in $stable references not existent record in $ttable\n"; - for ( my $i = 0; $i < @scols; $i++ ) { - print STDERR "\t$scols[$i] => '$set[$i]' => $tcols[$i]\n"; - } - print STDERR "\t". describe( $stable, $sid ) ."\n"; - $args{'action'}->( $sid, map { $scols[$_] => $set[$_] } (0 .. (@scols-1)) ) if $args{'action'}; - } -} - -sub describe { - my ($table, $id) = @_; - return '' unless my $cb = $describe_cb{ $table }; - - my $row = load_record( $table, $id ); - unless ( $row->{id} ) { - $table =~ s/s$//; - return "$table doesn't exist"; - } - return $cb->( $row ); -} - -sub columns_eq_cond { - my ($la, $lt, $lc, $ra, $rt, $rc) = @_; - my $ltype = $TYPE{"$lt.$lc"} || 'number'; - my $rtype = $TYPE{"$rt.$rc"} || 'number'; - return "$la.$lc = $ra.$rc" if $db_type ne 'Pg' || $ltype eq $rtype; - - if ( $rtype eq 'text' ) { - return "$ra.$rc LIKE CAST($la.$lc AS text)"; - } - elsif ( $ltype eq 'text' ) { - return "$la.$lc LIKE CAST($ra.$rc AS text)"; - } - else { die "don't know how to cast" } -} - -sub check_uniqueness { - my $on = shift; - my %args = @_; - - my @columns = @{ $args{'columns'} }; - - print "Checking uniqueness of ( ", join(', ', map "'$_'", @columns )," ) in table '$on'\n" - if $opt{'versbose'}; - - my ($scond, $tcond); - if ( $scond = $tcond = $args{'condition'} ) { - $scond =~ s/(\s|^)\./$1s./g; - $tcond =~ s/(\s|^)\./$1t./g; - } - - my $query = "SELECT s.id, t.id, ". join(', ', map "s.$_", @columns) - ." FROM $on s LEFT JOIN $on t " - ." ON s.id != t.id AND ". join(' AND ', map "s.$_ = t.$_", @columns) - . ($tcond? " AND ( $tcond )": "") - ." WHERE t.id IS NOT NULL " - ." AND ". join(' AND ', map "s.$_ IS NOT NULL", @columns); - $query .= " AND ( $scond )" if $scond; - - my $sth = execute_query( - $query, - $args{'bind_values'}? (@{ $args{'bind_values'} }, @{ $args{'bind_values'} }): () - ); - while ( my ($sid, $tid, @set) = $sth->fetchrow_array ) { - print STDERR "Record #$tid in $on has the same set of values as $sid\n"; - for ( my $i = 0; $i < @columns; $i++ ) { - print STDERR "\t$columns[$i] => '$set[$i]'\n"; - } - } -} - -sub load_record { - my ($table, $id) = @_; - my $sth = execute_query( "SELECT * FROM $table WHERE id = ?", $id ); - return $sth->fetchrow_hashref('NAME_lc'); -} - -sub delete_record { - my ($table, $id) = (@_); - print "Deleting record #$id in $table\n" if $opt{'verbose'}; - my $query = "DELETE FROM $table WHERE id = ?"; - $redo_check{ $_ } = 1 foreach @{ $redo_on{'Delete'}{ $table } || [] }; - return execute_query( $query, $id ); -} - -sub create_record { - print "Creating a record in $_[0]\n" if $opt{'verbose'}; - $redo_check{ $_ } = 1 foreach @{ $redo_on{'Create'}{ $_[0] } || [] }; - return $RT::Handle->Insert( @_ ); -} - -sub update_records { - my $table = shift; - my $where = shift; - my $what = shift; - - my (@where_cols, @where_binds); - while ( my ($k, $v) = each %$where ) { push @where_cols, $k; push @where_binds, $v; } - - my (@what_cols, @what_binds); - while ( my ($k, $v) = each %$what ) { push @what_cols, $k; push @what_binds, $v; } - - print "Updating record(s) in $table\n" if $opt{'verbose'}; - my $query = "UPDATE $table SET ". join(', ', map "$_ = ?", @what_cols) - ." WHERE ". join(' AND ', map "$_ = ?", @where_cols); - $redo_check{ $_ } = 1 foreach @{ $redo_on{'Update'}{ $table } || [] }; - return execute_query( $query, @what_binds, @where_binds ); -} - -sub execute_query { - my ($query, @binds) = @_; - - print "Executing query: $query\n\n" if $opt{'verbose'}; - - my $sth = $dbh->prepare( $query ) or die "couldn't prepare $query\n\tError: ". $dbh->errstr; - $sth->execute( @binds ) or die "couldn't execute $query\n\tError: ". $sth->errstr; - return $sth; -} - -{ my %cached_answer; -sub prompt { - my $action = shift; - my $msg = shift; - my $token = shift || join ':', caller; - - return 0 unless $opt{'resolve'}; - return 1 if $opt{'force'}; - - return $cached_answer{ $token } if exists $cached_answer{ $token }; - - print $msg, "\n"; - print "$action ALL records with the same defect? [N]: "; - my $a = ; - return $cached_answer{ $token } = 1 if $a =~ /^(y|yes)$/i; - return $cached_answer{ $token } = 0; -} } - -{ my %cached_answer; -sub prompt_action { - my $actions = shift; - my $msg = shift; - my $token = shift || join ':', caller; - - return '' unless $opt{'resolve'}; - return '' if $opt{'force'}; - return $cached_answer{ $token } if exists $cached_answer{ $token }; - - print $msg, "\n"; - print join( ' or ', @$actions ) ." ALL records with the same defect? [do nothing]: "; - my $a = ; - chomp $a; - return $cached_answer{ $token } = '' unless $a; - foreach ( grep rindex(lc $_, lc $a, 0) == 0, @$actions ) { - return $cached_answer{ $token } = lc substr $a, 0, 1; - } - return $cached_answer{ $token } = ''; -} } - -{ my %cached_answer; -sub prompt_integer { - my $action = shift; - my $msg = shift; - my $token = shift || join ':', caller; - - return 0 unless $opt{'resolve'}; - return 0 if $opt{'force'}; - - return $cached_answer{ $token } if exists $cached_answer{ $token }; - - print $msg, "\n"; - print "$action ALL records with the same defect? [0]: "; - my $a = ; chomp $a; $a = int($a); - return $cached_answer{ $token } = $a; -} } - -1; diff --git a/rt/t/data/configs/apache2.2+fastcgi.conf b/rt/t/data/configs/apache2.2+fastcgi.conf deleted file mode 100644 index 0a6945054..000000000 --- a/rt/t/data/configs/apache2.2+fastcgi.conf +++ /dev/null @@ -1,44 +0,0 @@ -ServerRoot %%SERVER_ROOT%% -PidFile %%PID_FILE%% -LockFile %%LOCK_FILE%% -ServerAdmin root@localhost - -%%LOAD_MODULES%% - -FastCgiIpcDir %%TMP_DIR%% - - - -User freeside -Group freeside - - - -Listen %%LISTEN%% - -ErrorLog "%%LOG_FILE%%" -LogLevel debug - - - Options FollowSymLinks - AllowOverride None - Order deny,allow - Deny from all - - -AddDefaultCharset UTF-8 - -FastCgiServer %%RT_BIN_PATH%%/mason_handler.fcgi -processes 1 -idle-timeout 180 -initial-env RT_SITE_CONFIG=%%RT_SITE_CONFIG%% - -Alias /NoAuth/images/ %%DOCUMENT_ROOT%%/NoAuth/images/ -ScriptAlias / %%RT_BIN_PATH%%/mason_handler.fcgi/ - -DocumentRoot "%%DOCUMENT_ROOT%%" - - Order allow,deny - Allow from all - - Options +ExecCGI - AddHandler fastcgi-script fcgi - - diff --git a/rt/t/data/configs/apache2.2+mod_perl.conf b/rt/t/data/configs/apache2.2+mod_perl.conf deleted file mode 100644 index 17761c532..000000000 --- a/rt/t/data/configs/apache2.2+mod_perl.conf +++ /dev/null @@ -1,41 +0,0 @@ -ServerRoot %%SERVER_ROOT%% -PidFile %%PID_FILE%% -LockFile %%LOCK_FILE%% -ServerAdmin root@localhost - -%%LOAD_MODULES%% - - - -User freeside -Group freeside - - - -Listen %%LISTEN%% - -ErrorLog "%%LOG_FILE%%" -LogLevel debug - - - Options FollowSymLinks - AllowOverride None - Order deny,allow - Deny from all - - -AddDefaultCharset UTF-8 -PerlSetEnv RT_SITE_CONFIG %%RT_SITE_CONFIG%% -PerlRequire %%RT_BIN_PATH%%/webmux.pl - -RedirectMatch permanent (.*)/$ $1/index.html - -DocumentRoot "%%DOCUMENT_ROOT%%" - - Order allow,deny - Allow from all - - SetHandler perl-script - PerlResponseHandler RT::Mason - - -- 2.11.0