diff options
Diffstat (limited to 'rt')
-rw-r--r-- | rt/FREESIDE_MODIFIED | 6 | ||||
-rw-r--r-- | rt/Makefile | 31 | ||||
-rwxr-xr-x | rt/README | 46 | ||||
-rwxr-xr-x | rt/bin/mason_handler.fcgi | 24 | ||||
-rwxr-xr-x | rt/bin/mason_handler.scgi | 8 | ||||
-rwxr-xr-x | rt/bin/rt | 1816 | ||||
-rwxr-xr-x | rt/bin/rt-mailgate | 109 | ||||
-rwxr-xr-x | rt/bin/webmux.pl | 29 | ||||
-rw-r--r-- | rt/config.layout.in (renamed from rt/config.layout) | 23 | ||||
-rw-r--r-- | rt/etc/RT_SiteConfig.pm | 12 | ||||
-rw-r--r-- | rt/etc/acl.Oracle | 12 | ||||
-rwxr-xr-x | rt/etc/acl.mysql | 2 | ||||
-rw-r--r-- | rt/etc/schema.Oracle | 354 | ||||
-rwxr-xr-x | rt/etc/schema.Pg | 16 | ||||
-rwxr-xr-x | rt/etc/schema.mysql | 10 | ||||
-rw-r--r-- | rt/lib/RT.pm | 33 | ||||
-rwxr-xr-x | rt/lib/RT/Action/Autoreply.pm | 16 | ||||
-rwxr-xr-x | rt/lib/RT/Action/SendEmail.pm | 40 | ||||
-rwxr-xr-x | rt/lib/RT/CurrentUser.pm | 49 | ||||
-rw-r--r-- | rt/lib/RT/Date.pm | 18 | ||||
-rw-r--r-- | rt/lib/RT/Handle.pm | 18 | ||||
-rw-r--r-- | rt/lib/RT/Interface/CLI.pm | 2 | ||||
-rwxr-xr-x | rt/lib/RT/Interface/Email.pm | 300 | ||||
-rw-r--r-- | rt/lib/RT/Interface/Web.pm | 152 | ||||
-rwxr-xr-x | rt/lib/RT/Record.pm | 5 | ||||
-rw-r--r-- | rt/lib/RT/URI/freeside.pm | 188 | ||||
-rw-r--r-- | rt/sbin/rt-setup-database | 5 | ||||
-rw-r--r-- | rt/sbin/rt-setup-database.in | 7 |
28 files changed, 495 insertions, 2836 deletions
diff --git a/rt/FREESIDE_MODIFIED b/rt/FREESIDE_MODIFIED new file mode 100644 index 000000000..3937c30fc --- /dev/null +++ b/rt/FREESIDE_MODIFIED @@ -0,0 +1,6 @@ +sbin/rt-setup-database +sbin/rt-setup-database.in +config.layout +config.layout.in +etc/RT_SiteConfig.pm +lib/RT/URI/freeside.pm diff --git a/rt/Makefile b/rt/Makefile index 0895874fb..644722109 100644 --- a/rt/Makefile +++ b/rt/Makefile @@ -1,19 +1,20 @@ # BEGIN LICENSE BLOCK # -# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com> +# Copyright (c) 1996-2002 Jesse Vincent <jesse@bestpractical.com> # # (Except where explictly superceded by other copyright notices) # # 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. +# 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. # +# # Unless otherwise specified, all modifications, corrections or # extensions to this work which alter its source code become the # property of Best Practical Solutions, LLC when submitted for @@ -21,6 +22,8 @@ # # # END LICENSE BLOCK + + # # DO NOT HAND-EDIT the file named 'Makefile'. This file is autogenerated. # Have a look at "configure" and "Makefile.in" instead @@ -36,7 +39,7 @@ SITE_CONFIG_FILE = $(CONFIG_FILE_PATH)/RT_SiteConfig.pm RT_VERSION_MAJOR = 3 RT_VERSION_MINOR = 0 -RT_VERSION_PATCH = 9 +RT_VERSION_PATCH = 4 RT_VERSION = $(RT_VERSION_MAJOR).$(RT_VERSION_MINOR).$(RT_VERSION_PATCH) TAG = rt-$(RT_VERSION_MAJOR)-$(RT_VERSION_MINOR)-$(RT_VERSION_PATCH) @@ -98,8 +101,8 @@ RT_MODPERL_HANDLER = $(RT_BIN_PATH)/webmux.pl RT_FASTCGI_HANDLER = $(RT_BIN_PATH)/mason_handler.fcgi # RT_WIN32_FASTCGI_HANDLER is the mason handler script for FastCGI RT_WIN32_FASTCGI_HANDLER = $(RT_BIN_PATH)/mason_handler.svc -# RT's CLI -RT_CLI_BIN = $(RT_BIN_PATH)/rt +# RT's admin CLI +RT_CLI_ADMIN_BIN = $(RT_BIN_PATH)/rtadmin # RT's mail gateway RT_MAILGATE_BIN = $(RT_BIN_PATH)/rt-mailgate # RT's cron tool @@ -112,7 +115,6 @@ SETGID_BINARIES = $(DESTDIR)/$(RT_FASTCGI_HANDLER) \ BINARIES = $(DESTDIR)/$(RT_MODPERL_HANDLER) \ $(DESTDIR)/$(RT_MAILGATE_BIN) \ - $(DESTDIR)/$(RT_CLI_BIN) \ $(DESTDIR)/$(RT_CRON_BIN) \ $(SETGID_BINARIES) SYSTEM_BINARIES = $(DESTDIR)/$(RT_SBIN_PATH)/ @@ -126,7 +128,6 @@ SYSTEM_BINARIES = $(DESTDIR)/$(RT_SBIN_PATH)/ # DB_TYPE defines what sort of database RT trys to talk to # "mysql" is known to work. # "Pg" is known to work -# "Informix" is known to work DB_TYPE = mysql @@ -137,8 +138,7 @@ DB_TYPE = mysql # For mysql, you probably want 'root' # For Pg, you probably want 'postgres' -# For Oracle, you want 'system' -# For Informix, you want 'informix' +# For oracle, you want 'system' DB_DBA = root @@ -211,7 +211,7 @@ upgrade-instruct: @echo " $(RT_SBIN_PATH)/rt-setup-database --action insert --datafile etc/upgrade/<version>" -upgrade: config-install dirs files-install fixperms upgrade-instruct +upgrade: dirs upgrade-noclobber upgrade-instruct upgrade-noclobber: config-install libs-install html-install bin-install local-install doc-install fixperms @@ -312,16 +312,13 @@ config-install: test: $(PERL) -Ilib lib/t/00smoke.t -regression-install: config-install - $(PERL) -pi -e 's/Set\(\$$DatabaseName.*\);/Set\(\$$DatabaseName, "rt3regression"\);/' $(DESTDIR)/$(CONFIG_FILE) - -regression-nosetgid-quiet: regression-install dirs files-install libs-install sbin-install bin-install regression-instruct regression-reset-db testify-pods fixperms-nosetgid apachectl +regression-nosetgid-quiet: config-install dirs files-install libs-install sbin-install bin-install regression-instruct regression-reset-db testify-pods fixperms-nosetgid apachectl $(PERL) sbin/regression_harness -regression-nosetgid: regression-install dirs files-install libs-install sbin-install bin-install regression-instruct regression-reset-db testify-pods fixperms-nosetgid apachectl +regression-nosetgid: config-install dirs files-install libs-install sbin-install bin-install regression-instruct regression-reset-db testify-pods fixperms-nosetgid apachectl $(PERL) lib/t/02regression.t -regression: regression-install dirs files-install libs-install sbin-install bin-install regression-instruct regression-reset-db testify-pods fixperms apachectl +regression: config-install dirs files-install libs-install sbin-install bin-install regression-instruct regression-reset-db testify-pods apachectl $(PERL) lib/t/02regression.t regression-quiet: @@ -400,9 +397,7 @@ bin-install: -cp -rp \ bin/rt-mailgate \ bin/mason_handler.fcgi \ - bin/mason_handler.scgi \ bin/mason_handler.svc \ - bin/rt \ bin/webmux.pl \ bin/rt-crontool \ $(DESTDIR)/$(RT_BIN_PATH) @@ -21,7 +21,6 @@ # # # END LICENSE BLOCK - RT is an enterprise-grade issue tracking system. It allows organizations to keep track of their to-do lists, who is working on which tasks, what's already been done, and when tasks were @@ -37,22 +36,22 @@ up and use. REQUIRED PACKAGES: ------------------ -o Perl 5.8.3 or later (http://www.perl.com). +o Perl 5.8.0 or later (http://www.perl.com). (If you intend to use the FastCGI or SpeedyCGI support, you need to make sure that perl has been built with support for setgid perl scripts.)` - Perl versions prior to 5.8.3 contain bugs that could result in data - corruption. We recommend strongly that you use 5.8.3 or newer. - Perl 5.6.1 is currently deprecated and will be officially desupported in a future release o A DB backend; MySQL is recommended ( http://www.mysql.com ) Currently supported: Mysql 4.0.13 or later. Postgres 7.2 or later. - Oracle 9iR2. + + Mysql 3.23.46 or newer with support for InnoDB + is currently deprecated and will be officially + desupported in a future release. o Apache version 1.3.x or 2.x (http://httpd.apache.org) with mod_perl -- (http://perl.apache.org ) @@ -120,7 +119,7 @@ http://www.bestpractical.com/rt perl sbin/rt-test-dependencies \ --with-<databasename> --with-<web-environment> - databasename is one of: mysql, postgres, oracle + databasename is one of: mysql, postgres web-environment is one of: fastcgi, modperl1, modperl2 3.2 If there are unsatisfied dependencies, install them by hand or run: @@ -152,10 +151,6 @@ http://www.bestpractical.com/rt 5b FOR UPGRADING: (Within the RT 3.0.x series) - - Read through the UPGRADING document included in this distribution. - It may contain important instructions for updating your database - As root, type: make upgrade (replace "make" with the local name for Make, if you need to) @@ -165,14 +160,6 @@ http://www.bestpractical.com/rt It may then instruct you to update your RT system database objects -5c FOR UPGRADING: (From RT 2.0.x) - - Download the RT2 to RT3 migration tools from: - - http://bestpractical.com/pub/rt/devel/rt2-to-rt3.tar.gz - - Follow the included instructions. - 6 Edit etc/RT_SiteConfig.pm in your RT installation directory, by specifying any values you need to change from the defaults in etc/RT_Config.pm @@ -205,20 +192,31 @@ Apache DocumentRoot /opt/rt3/share/html AddDefaultCharset UTF-8 - # these four lines apply to Apache2+mod_perl2 only: {{{ - PerlSetVar MasonArgsMethod CGI + # this line applies to Apache2+mod_perl2 only PerlModule Apache2 Apache::compat - RewriteEngine On - RewriteRule ^(.*)/$ $1/index.html - # }}} PerlModule Apache::DBI PerlRequire /opt/rt3/bin/webmux.pl + # this section applies to Apache 1 only <Location /> SetHandler perl-script PerlHandler RT::Mason </Location> + + # this section applies to Apache2+mod_perl2 only + <FilesMatch "\.html$"> + SetHandler perl-script + PerlHandler RT::Mason + </FilesMatch> + <LocationMatch "/Attachment/"> + SetHandler perl-script + PerlHandler RT::Mason + </LocationMatch> + <LocationMatch "/REST/"> + SetHandler perl-script + PerlHandler RT::Mason + </LocationMatch> </VirtualHost> diff --git a/rt/bin/mason_handler.fcgi b/rt/bin/mason_handler.fcgi index 93d1f8855..431eccbd3 100755 --- a/rt/bin/mason_handler.fcgi +++ b/rt/bin/mason_handler.fcgi @@ -27,7 +27,7 @@ use strict; use File::Basename; require ('/opt/rt3/bin/webmux.pl'); -my $h = &RT::Interface::Web::NewCGIHandler(@RT::MasonParameters); +my $h = &RT::Interface::Web::NewCGIHandler(); # Enter CGI::Fast mode, which should also work as a vanilla CGI script. require CGI::Fast; @@ -44,25 +44,11 @@ while ( my $cgi = CGI::Fast->new ) { $ENV{'ENV'} = '' if defined $ENV{'ENV'}; $ENV{'IFS'} = '' if defined $ENV{'IFS'}; - RT::ConnectToDatabase(); - - if ( ( !$h->interp->comp_exists( $cgi->path_info ) ) - && ( $h->interp->comp_exists( $cgi->path_info . "/index.html" ) ) ) { - $cgi->path_info( $cgi->path_info . "/index.html" ); - } - - eval { $h->handle_cgi_object($cgi); }; - if ($@) { - $RT::Logger->crit($@); - } - - - if ($RT::Handle->TransactionDepth) { - $RT::Handle->ForceRollback; - $RT::Logger->crit("Transaction not committed. Usually indicates a software fault. Data loss may have occurred") ; + unless ($h->interp->comp_exists($cgi->path_info)) { + $cgi->path_info($cgi->path_info . "/index.html"); } - - + $h->handle_cgi_object($cgi); + # _should_ always be tied } 1; diff --git a/rt/bin/mason_handler.scgi b/rt/bin/mason_handler.scgi index 7774189ee..8e1135c2f 100755 --- a/rt/bin/mason_handler.scgi +++ b/rt/bin/mason_handler.scgi @@ -26,18 +26,16 @@ use strict; require ('/opt/rt3/bin/webmux.pl'); -my $h = &RT::Interface::Web::NewCGIHandler(@RT::MasonParameters); +my $h = &RT::Interface::Web::NewCGIHandler(); require CGI; RT::Init(); my $cgi = CGI->new; -if ( ( !$h->interp->comp_exists( $cgi->path_info ) ) - && ( $h->interp->comp_exists( $cgi->path_info . "/index.html" ) ) ) { - $cgi->path_info( $cgi->path_info . "/index.html" ); +unless ($h->interp->comp_exists($cgi->path_info)) { + $cgi->path_info($cgi->path_info . "/index.html"); } - $h->handle_cgi_object($cgi); 1; diff --git a/rt/bin/rt b/rt/bin/rt deleted file mode 100755 index d9f8a3f05..000000000 --- a/rt/bin/rt +++ /dev/null @@ -1,1816 +0,0 @@ -#!/usr/bin/perl -w -# BEGIN LICENSE BLOCK -# -# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com> -# -# (Except where explictly superceded by other copyright notices) -# -# 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. -# -# Unless otherwise specified, all modifications, corrections or -# extensions to this work which alter its source code become the -# property of Best Practical Solutions, LLC when submitted for -# inclusion in the work. -# -# -# END LICENSE BLOCK - -use strict; - -# This program is intentionally written to have as few non-core module -# dependencies as possible. It should stay that way. - -use Cwd; -use LWP; -use HTTP::Request::Common; - -# We derive configuration information from hardwired defaults, dotfiles, -# and the RT* environment variables (in increasing order of precedence). -# Session information is stored in ~/.rt_sessions. - -my $VERSION = 0.02; -my $HOME = eval{(getpwuid($<))[7]} - || $ENV{HOME} || $ENV{LOGDIR} || $ENV{HOMEPATH} - || "."; -my %config = ( - ( - debug => 0, - user => eval{(getpwuid($<))[0]} || $ENV{USER} || $ENV{USERNAME}, - passwd => undef, - server => 'http://localhost/rt/', - ), - config_from_file($ENV{RTCONFIG} || ".rtrc"), - config_from_env() -); -my $session = new Session("$HOME/.rt_sessions"); -my $REST = "$config{server}/REST/1.0"; - -sub whine; -sub DEBUG { warn @_ if $config{debug} >= shift } - -# These regexes are used by command handlers to parse arguments. -# (XXX: Ask Autrijus how i18n changes these definitions.) - -my $name = '[\w.-]+'; -my $field = '[a-zA-Z][a-zA-Z0-9_-]*'; -my $label = '[a-zA-Z0-9@_.+-]+'; -my $labels = "(?:$label,)*$label"; -my $idlist = '(?:(?:\d+-)?\d+,)*(?:\d+-)?\d+'; - -# Our command line looks like this: -# -# rt <action> [options] [arguments] -# -# We'll parse just enough of it to decide upon an action to perform, and -# leave the rest to per-action handlers to interpret appropriately. - -my %handlers = ( -# handler => [ ...aliases... ], - version => ["version", "ver"], - logout => ["logout"], - help => ["help", "man"], - show => ["show", "cat"], - edit => ["create", "edit", "new", "ed"], - list => ["search", "list", "ls"], - comment => ["comment", "correspond"], - link => ["link", "ln"], - merge => ["merge"], - grant => ["grant", "revoke"], -); - -# Once we find and call an appropriate handler, we're done. - -my (%actions, $action); -foreach my $fn (keys %handlers) { - foreach my $alias (@{ $handlers{$fn} }) { - $actions{$alias} = \&{"$fn"}; - } -} -if (@ARGV && exists $actions{$ARGV[0]}) { - $action = shift @ARGV; -} -$actions{$action || "help"}->($action || ()); -exit; - -# Handler functions. -# ------------------ -# -# The following subs are handlers for each entry in %actions. - -sub version { - print "rt $VERSION\n"; -} - -sub logout { - submit("$REST/logout") if defined $session->cookie; -} - -sub help { - my ($action, $type) = @_; - my (%help, $key); - - # What help topics do we know about? - local $/ = undef; - foreach my $item (@{ Form::parse(<DATA>) }) { - my $title = $item->[2]{Title}; - my @titles = ref $title eq 'ARRAY' ? @$title : $title; - - foreach $title (grep $_, @titles) { - $help{$title} = $item->[2]{Text}; - } - } - - # What does the user want help with? - undef $action if ($action && $actions{$action} eq \&help); - unless ($action || $type) { - # If we don't know, we'll look for clues in @ARGV. - foreach (@ARGV) { - if (exists $help{$_}) { $key = $_; last; } - } - unless ($key) { - # Tolerate possibly plural words. - foreach (@ARGV) { - if ($_ =~ s/s$// && exists $help{$_}) { $key = $_; last; } - } - } - } - - if ($type && $action) { - $key = "$type.$action"; - } - $key ||= $type || $action || "introduction"; - - # Find a suitable topic to display. - while (!exists $help{$key}) { - if ($type && $action) { - if ($key eq "$type.$action") { $key = $action; } - elsif ($key eq $action) { $key = $type; } - else { $key = "introduction"; } - } - else { - $key = "introduction"; - } - } - - print STDERR $help{$key}, "\n\n"; -} - -# Displays a list of objects that match some specified condition. - -sub list { - my ($q, $type, %data, $orderby); - my $bad = 0; - - while (@ARGV) { - $_ = shift @ARGV; - - if (/^-t$/) { - $bad = 1, last unless defined($type = get_type_argument()); - } - elsif (/^-S$/) { - $bad = 1, last unless get_var_argument(\%data); - } - elsif (/^-o$/) { - $orderby = shift @ARGV; - } - elsif (/^-([isl])$/) { - $data{format} = $1; - } - elsif (/^-f$/) { - if ($ARGV[0] !~ /^(?:(?:$field,)*$field)$/) { - whine "No valid field list in '-f $ARGV[0]'."; - $bad = 1; last; - } - $data{fields} = shift @ARGV; - } - elsif (!defined $q && !/^-/) { - $q = $_; - } - else { - my $datum = /^-/ ? "option" : "argument"; - whine "Unrecognised $datum '$_'."; - $bad = 1; last; - } - } - - $type ||= "ticket"; - unless ($type && defined $q) { - my $item = $type ? "query string" : "object type"; - whine "No $item specified."; - $bad = 1; - } - return help("list", $type) if $bad; - - my $r = submit("$REST/search/$type", { query => $q, %data, orderby => $orderby || "" }); - print $r->content; -} - -# Displays selected information about a single object. - -sub show { - my ($type, @objects, %data); - my $slurped = 0; - my $bad = 0; - - while (@ARGV) { - $_ = shift @ARGV; - - if (/^-t$/) { - $bad = 1, last unless defined($type = get_type_argument()); - } - elsif (/^-S$/) { - $bad = 1, last unless get_var_argument(\%data); - } - elsif (/^-([isl])$/) { - $data{format} = $1; - } - elsif (/^-$/ && !$slurped) { - chomp(my @lines = <STDIN>); - foreach (@lines) { - unless (is_object_spec($_, $type)) { - whine "Invalid object on STDIN: '$_'."; - $bad = 1; last; - } - push @objects, $_; - } - $slurped = 1; - } - elsif (/^-f$/) { - if ($ARGV[0] !~ /^(?:(?:$field,)*$field)$/) { - whine "No valid field list in '-f $ARGV[0]'."; - $bad = 1; last; - } - $data{fields} = shift @ARGV; - } - elsif (my $spec = is_object_spec($_, $type)) { - push @objects, $spec; - } - else { - my $datum = /^-/ ? "option" : "argument"; - whine "Unrecognised $datum '$_'."; - $bad = 1; last; - } - } - - unless (@objects) { - whine "No objects specified."; - $bad = 1; - } - return help("show", $type) if $bad; - - my $r = submit("$REST/show", { id => \@objects, %data }); - print $r->content; -} - -# To create a new object, we ask the server for a form with the defaults -# filled in, allow the user to edit it, and send the form back. -# -# To edit an object, we must ask the server for a form representing that -# object, make changes requested by the user (either on the command line -# or interactively via $EDITOR), and send the form back. - -sub edit { - my ($action) = @_; - my (%data, $type, @objects); - my ($cl, $text, $edit, $input, $output); - - use vars qw(%set %add %del); - %set = %add = %del = (); - my $slurped = 0; - my $bad = 0; - - while (@ARGV) { - $_ = shift @ARGV; - - if (/^-e$/) { $edit = 1 } - elsif (/^-i$/) { $input = 1 } - elsif (/^-o$/) { $output = 1 } - elsif (/^-t$/) { - $bad = 1, last unless defined($type = get_type_argument()); - } - elsif (/^-S$/) { - $bad = 1, last unless get_var_argument(\%data); - } - elsif (/^-$/ && !($slurped || $input)) { - chomp(my @lines = <STDIN>); - foreach (@lines) { - unless (is_object_spec($_, $type)) { - whine "Invalid object on STDIN: '$_'."; - $bad = 1; last; - } - push @objects, $_; - } - $slurped = 1; - } - elsif (/^set$/i) { - my $vars = 0; - - while (@ARGV && $ARGV[0] =~ /^($field)([+-]?=)(.*)$/) { - my ($key, $op, $val) = ($1, $2, $3); - my $hash = ($op eq '=') ? \%set : ($op =~ /^\+/) ? \%add : \%del; - - vpush($hash, lc $key, $val); - shift @ARGV; - $vars++; - } - unless ($vars) { - whine "No variables to set."; - $bad = 1; last; - } - $cl = $vars; - } - elsif (/^(?:add|del)$/i) { - my $vars = 0; - my $hash = ($_ eq "add") ? \%add : \%del; - - while (@ARGV && $ARGV[0] =~ /^($field)=(.*)$/) { - my ($key, $val) = ($1, $2); - - vpush($hash, lc $key, $val); - shift @ARGV; - $vars++; - } - unless ($vars) { - whine "No variables to set."; - $bad = 1; last; - } - $cl = $vars; - } - elsif (my $spec = is_object_spec($_, $type)) { - push @objects, $spec; - } - else { - my $datum = /^-/ ? "option" : "argument"; - whine "Unrecognised $datum '$_'."; - $bad = 1; last; - } - } - - if ($action =~ /^ed(?:it)?$/) { - unless (@objects) { - whine "No objects specified."; - $bad = 1; - } - } - else { - if (@objects) { - whine "You shouldn't specify objects as arguments to $action."; - $bad = 1; - } - unless ($type) { - whine "What type of object do you want to create?"; - $bad = 1; - } - @objects = ("$type/new"); - } - return help($action, $type) if $bad; - - # We need a form to make changes to. We usually ask the server for - # one, but we can avoid that if we are fed one on STDIN, or if the - # user doesn't want to edit the form by hand, and the command line - # specifies only simple variable assignments. - - if ($input) { - local $/ = undef; - $text = <STDIN>; - } - elsif ($edit || %add || %del || !$cl) { - my $r = submit("$REST/show", { id => \@objects, format => 'l' }); - $text = $r->content; - } - - # If any changes were specified on the command line, apply them. - if ($cl) { - if ($text) { - # We're updating forms from the server. - my $forms = Form::parse($text); - - foreach my $form (@$forms) { - my ($c, $o, $k, $e) = @$form; - my ($key, $val); - - next if ($e || !@$o); - - local %add = %add; - local %del = %del; - local %set = %set; - - # Make changes to existing fields. - foreach $key (@$o) { - if (exists $add{lc $key}) { - $val = delete $add{lc $key}; - vpush($k, $key, $val); - $k->{$key} = vsplit($k->{$key}) if $val =~ /[,\n]/; - } - if (exists $del{lc $key}) { - $val = delete $del{lc $key}; - my %val = map {$_=>1} @{ vsplit($val) }; - $k->{$key} = vsplit($k->{$key}); - @{$k->{$key}} = grep {!exists $val{$_}} @{$k->{$key}}; - } - if (exists $set{lc $key}) { - $k->{$key} = delete $set{lc $key}; - } - } - - # Then update the others. - foreach $key (keys %set) { vpush($k, $key, $set{$key}) } - foreach $key (keys %add) { - vpush($k, $key, $add{$key}); - $k->{$key} = vsplit($k->{$key}); - } - push @$o, (keys %add, keys %set); - } - - $text = Form::compose($forms); - } - else { - # We're rolling our own set of forms. - my @forms; - foreach (@objects) { - my ($type, $ids, $args) = - m{^($name)/($idlist|$labels)(?:(/.*))?$}o; - - $args ||= ""; - foreach my $obj (expand_list($ids)) { - my %set = (%set, id => "$type/$obj$args"); - push @forms, ["", [keys %set], \%set]; - } - } - $text = Form::compose(\@forms); - } - } - - if ($output) { - print $text; - exit; - } - - my $synerr = 0; - -EDIT: - # We'll let the user edit the form before sending it to the server, - # unless we have enough information to submit it non-interactively. - if ($edit || (!$input && !$cl)) { - my $newtext = vi($text); - # We won't resubmit a bad form unless it was changed. - $text = ($synerr && $newtext eq $text) ? undef : $newtext; - } - - if ($text) { - my $r = submit("$REST/edit", {content => $text, %data}); - if ($r->code == 409) { - # If we submitted a bad form, we'll give the user a chance - # to correct it and resubmit. - if ($edit || (!$input && !$cl)) { - $text = $r->content; - $synerr = 1; - goto EDIT; - } - else { - print $r->content; - exit -1; - } - } - print $r->content; - } -} - -# We roll "comment" and "correspond" into the same handler. - -sub comment { - my ($action) = @_; - my (%data, $id, @files, @bcc, @cc, $msg, $wtime, $edit); - my $bad = 0; - - while (@ARGV) { - $_ = shift @ARGV; - - if (/^-e$/) { - $edit = 1; - } - elsif (/^-[abcmw]$/) { - unless (@ARGV) { - whine "No argument specified with $_."; - $bad = 1; last; - } - - if (/-a/) { - unless (-f $ARGV[0] && -r $ARGV[0]) { - whine "Cannot read attachment: '$ARGV[0]'."; - exit -1; - } - push @files, shift @ARGV; - } - elsif (/-([bc])/) { - my $a = $_ eq "-b" ? \@bcc : \@cc; - @$a = split /\s*,\s*/, shift @ARGV; - } - elsif (/-m/) { $msg = shift @ARGV } - elsif (/-w/) { $wtime = shift @ARGV } - } - elsif (!$id && m|^(?:ticket/)?($idlist)$|) { - $id = $1; - } - else { - my $datum = /^-/ ? "option" : "argument"; - whine "Unrecognised $datum '$_'."; - $bad = 1; last; - } - } - - unless ($id) { - whine "No object specified."; - $bad = 1; - } - return help($action, "ticket") if $bad; - - my $form = [ - "", - [ "Ticket", "Action", "Cc", "Bcc", "Attachment", "TimeWorked", "Text" ], - { - Ticket => $id, - Action => $action, - Cc => [ @cc ], - Bcc => [ @bcc ], - Attachment => [ @files ], - TimeWorked => $wtime || '', - Text => $msg || '', - } - ]; - - my $text = Form::compose([ $form ]); - - if ($edit || !$msg) { - my $error = 0; - my ($c, $o, $k, $e); - - do { - my $ntext = vi($text); - exit if ($error && $ntext eq $text); - $text = $ntext; - $form = Form::parse($text); - $error = 0; - - ($c, $o, $k, $e) = @{ $form->[0] }; - if ($e) { - $error = 1; - $c = "# Syntax error."; - goto NEXT; - } - elsif (!@$o) { - exit; - } - @files = @{ vsplit($k->{Attachment}) }; - - NEXT: - $text = Form::compose([[$c, $o, $k, $e]]); - } while ($error); - } - - my $i = 1; - foreach my $file (@files) { - $data{"attachment_$i"} = bless([ $file ], "Attachment"); - $i++; - } - $data{content} = $text; - - my $r = submit("$REST/ticket/comment/$id", \%data); - print $r->content; -} - -# Merge one ticket into another. - -sub merge { - my @id; - my $bad = 0; - - while (@ARGV) { - $_ = shift @ARGV; - - if (/^\d+$/) { - push @id, $_; - } - else { - whine "Unrecognised argument: '$_'."; - $bad = 1; last; - } - } - - unless (@id == 2) { - my $evil = @id > 2 ? "many" : "few"; - whine "Too $evil arguments specified."; - $bad = 1; - } - return help("merge", "ticket") if $bad; - - my $r = submit("$REST/ticket/merge/$id[0]", {into => $id[1]}); - print $r->content; -} - -# Link one ticket to another. - -sub link { - my ($bad, $del, %data) = (0, 0, ()); - my %ltypes = map { lc $_ => $_ } qw(DependsOn DependedOnBy RefersTo - ReferredToBy HasMember MemberOf); - - while (@ARGV && $ARGV[0] =~ /^-/) { - $_ = shift @ARGV; - - if (/^-d$/) { - $del = 1; - } - else { - whine "Unrecognised option: '$_'."; - $bad = 1; last; - } - } - - if (@ARGV == 3) { - my ($from, $rel, $to) = @ARGV; - if ($from !~ /^\d+$/ || $to !~ /^\d+$/) { - my $bad = $from =~ /^\d+$/ ? $to : $from; - whine "Invalid ticket ID '$bad' specified."; - $bad = 1; - } - unless (exists $ltypes{lc $rel}) { - whine "Invalid relationship '$rel' specified."; - $bad = 1; - } - %data = (id => $from, rel => $rel, to => $to, del => $del); - } - else { - my $bad = @ARGV < 3 ? "few" : "many"; - whine "Too $bad arguments specified."; - $bad = 1; - } - return help("link", "ticket") if $bad; - - my $r = submit("$REST/ticket/link", \%data); - print $r->content; -} - -# Grant/revoke a user's rights. - -sub grant { - my ($cmd) = @_; - - my $revoke = 0; - while (@ARGV) { - } - - $revoke = 1 if $cmd->{action} eq 'revoke'; -} - -# Client <-> Server communication. -# -------------------------------- -# -# This function composes and sends an HTTP request to the RT server, and -# interprets the response. It takes a request URI, and optional request -# data (a string, or a reference to a set of key-value pairs). - -sub submit { - my ($uri, $content) = @_; - my ($req, $data); - my $ua = new LWP::UserAgent(agent => "RT/3.0b", env_proxy => 1); - - # Did the caller specify any data to send with the request? - $data = []; - if (defined $content) { - unless (ref $content) { - # If it's just a string, make sure LWP handles it properly. - # (By pretending that it's a file!) - $content = [ content => [undef, "", Content => $content] ]; - } - elsif (ref $content eq 'HASH') { - my @data; - foreach my $k (keys %$content) { - if (ref $content->{$k} eq 'ARRAY') { - foreach my $v (@{ $content->{$k} }) { - push @data, $k, $v; - } - } - else { push @data, $k, $content->{$k} } - } - $content = \@data; - } - $data = $content; - } - - # Should we send authentication information to start a new session? - if (!defined $session->cookie) { - push @$data, ( user => $config{user} ); - push @$data, ( pass => $config{passwd} || read_passwd() ); - } - - # Now, we construct the request. - if (@$data) { - $req = POST($uri, $data, Content_Type => 'form-data'); - } - else { - $req = GET($uri); - } - $session->add_cookie_header($req); - - # Then we send the request and parse the response. - DEBUG(3, $req->as_string); - my $res = $ua->request($req); - DEBUG(3, $res->as_string); - - if ($res->is_success) { - # The content of the response we get from the RT server consists - # of an HTTP-like status line followed by optional header lines, - # a blank line, and arbitrary text. - - my ($head, $text) = split /\n\n/, $res->content, 2; - my ($status, @headers) = split /\n/, $head; - $text =~ s/\n*$/\n/; - - # "RT/3.0.1 401 Credentials required" - if ($status !~ m#^RT/\d+(?:\.\d+)+(?:-?\w+)? (\d+) ([\w\s]+)$#) { - warn "rt: Malformed RT response from $config{server}.\n"; - warn "(Rerun with RTDEBUG=3 for details.)\n" if $config{debug} < 3; - exit -1; - } - - # Our caller can pretend that the server returned a custom HTTP - # response code and message. (Doing that directly is apparently - # not sufficiently portable and uncomplicated.) - $res->code($1); - $res->message($2); - $res->content($text); - $session->update($res) if ($res->is_success || $res->code != 401); - - if (!$res->is_success) { - # We can deal with authentication failures ourselves. Either - # we sent invalid credentials, or our session has expired. - if ($res->code == 401) { - my %d = @$data; - if (exists $d{user}) { - warn "rt: Incorrect username or password.\n"; - exit -1; - } - elsif ($req->header("Cookie")) { - # We'll retry the request with credentials, unless - # we only wanted to logout in the first place. - $session->delete; - return submit(@_) unless $uri eq "$REST/logout"; - } - } - # Conflicts should be dealt with by the handler and user. - # For anything else, we just die. - elsif ($res->code != 409) { - warn "rt: ", $res->content; - exit; - } - } - } - else { - warn "rt: Server error: ", $res->message, " (", $res->code, ")\n"; - exit -1; - } - - return $res; -} - -# Session management. -# ------------------- -# -# Maintains a list of active sessions in the ~/.rt_sessions file. -{ - package Session; - my ($s, $u); - - # Initialises the session cache. - sub new { - my ($class, $file) = @_; - my $self = { - file => $file || "$HOME/.rt_sessions", - sids => { } - }; - - # The current session is identified by the currently configured - # server and user. - ($s, $u) = @config{"server", "user"}; - - bless $self, $class; - $self->load(); - - return $self; - } - - # Returns the current session cookie. - sub cookie { - my ($self) = @_; - my $cookie = $self->{sids}{$s}{$u}; - return defined $cookie ? "RT_SID=$cookie" : undef; - } - - # Deletes the current session cookie. - sub delete { - my ($self) = @_; - delete $self->{sids}{$s}{$u}; - } - - # Adds a Cookie header to an outgoing HTTP request. - sub add_cookie_header { - my ($self, $request) = @_; - my $cookie = $self->cookie(); - - $request->header(Cookie => $cookie) if defined $cookie; - } - - # Extracts the Set-Cookie header from an HTTP response, and updates - # session information accordingly. - sub update { - my ($self, $response) = @_; - my $cookie = $response->header("Set-Cookie"); - - if (defined $cookie && $cookie =~ /^RT_SID=([0-9A-Fa-f]+);/) { - $self->{sids}{$s}{$u} = $1; - } - } - - # Loads the session cache from the specified file. - sub load { - my ($self, $file) = @_; - $file ||= $self->{file}; - local *F; - - open(F, $file) && do { - $self->{file} = $file; - my $sids = $self->{sids} = {}; - while (<F>) { - chomp; - next if /^$/ || /^#/; - next unless m#^https?://[^ ]+ \w+ [0-9A-Fa-f]+$#; - my ($server, $user, $cookie) = split / /, $_; - $sids->{$server}{$user} = $cookie; - } - return 1; - }; - return 0; - } - - # Writes the current session cache to the specified file. - sub save { - my ($self, $file) = shift; - $file ||= $self->{file}; - local *F; - - open(F, ">$file") && do { - my $sids = $self->{sids}; - foreach my $server (keys %$sids) { - foreach my $user (keys %{ $sids->{$server} }) { - my $sid = $sids->{$server}{$user}; - if (defined $sid) { - print F "$server $user $sid\n"; - } - } - } - close(F); - chmod 0600, $file; - return 1; - }; - return 0; - } - - sub DESTROY { - my $self = shift; - $self->save; - } -} - -# Form handling. -# -------------- -# -# Forms are RFC822-style sets of (field, value) specifications with some -# initial comments and interspersed blank lines allowed for convenience. -# Sets of forms are separated by --\n (in a cheap parody of MIME). -# -# Each form is parsed into an array with four elements: commented text -# at the start of the form, an array with the order of keys, a hash with -# key/value pairs, and optional error text if the form syntax was wrong. - -# Returns a reference to an array of parsed forms. -sub Form::parse { - my $state = 0; - my @forms = (); - my @lines = split /\n/, $_[0]; - my ($c, $o, $k, $e) = ("", [], {}, ""); - - LINE: - while (@lines) { - my $line = shift @lines; - - next LINE if $line eq ''; - - if ($line eq '--') { - # We reached the end of one form. We'll ignore it if it was - # empty, and store it otherwise, errors and all. - if ($e || $c || @$o) { - push @forms, [ $c, $o, $k, $e ]; - $c = ""; $o = []; $k = {}; $e = ""; - } - $state = 0; - } - elsif ($state != -1) { - if ($state == 0 && $line =~ /^#/) { - # Read an optional block of comments (only) at the start - # of the form. - $state = 1; - $c = $line; - while (@lines && $lines[0] =~ /^#/) { - $c .= "\n".shift @lines; - } - $c .= "\n"; - } - elsif ($state <= 1 && $line =~ /^($field):(?:\s+(.*))?$/) { - # Read a field: value specification. - my $f = $1; - my @v = ($2 || ()); - - # Read continuation lines, if any. - while (@lines && ($lines[0] eq '' || $lines[0] =~ /^\s+/)) { - push @v, shift @lines; - } - pop @v while (@v && $v[-1] eq ''); - - # Strip longest common leading indent from text. - my $ws = ""; - foreach my $ls (map {/^(\s+)/} @v[1..$#v]) { - $ws = $ls if (!$ws || length($ls) < length($ws)); - } - s/^$ws// foreach @v; - - push(@$o, $f) unless exists $k->{$f}; - vpush($k, $f, join("\n", @v)); - - $state = 1; - } - elsif ($line !~ /^#/) { - # We've found a syntax error, so we'll reconstruct the - # form parsed thus far, and add an error marker. (>>) - $state = -1; - $e = Form::compose([[ "", $o, $k, "" ]]); - $e.= $line =~ /^>>/ ? "$line\n" : ">> $line\n"; - } - } - else { - # We saw a syntax error earlier, so we'll accumulate the - # contents of this form until the end. - $e .= "$line\n"; - } - } - push(@forms, [ $c, $o, $k, $e ]) if ($e || $c || @$o); - - foreach my $l (keys %$k) { - $k->{$l} = vsplit($k->{$l}) if (ref $k->{$l} eq 'ARRAY'); - } - - return \@forms; -} - -# Returns text representing a set of forms. -sub Form::compose { - my ($forms) = @_; - my @text; - - foreach my $form (@$forms) { - my ($c, $o, $k, $e) = @$form; - my $text = ""; - - if ($c) { - $c =~ s/\n*$/\n/; - $text = "$c\n"; - } - if ($e) { - $text .= $e; - } - elsif ($o) { - my @lines; - - foreach my $key (@$o) { - my ($line, $sp); - my $v = $k->{$key}; - my @values = ref $v eq 'ARRAY' ? @$v : $v; - - $sp = " "x(length("$key: ")); - $sp = " "x4 if length($sp) > 16; - - foreach $v (@values) { - if ($v =~ /\n/) { - $v =~ s/^/$sp/gm; - $v =~ s/^$sp//; - - if ($line) { - push @lines, "$line\n\n"; - $line = ""; - } - elsif (@lines && $lines[-1] !~ /\n\n$/) { - $lines[-1] .= "\n"; - } - push @lines, "$key: $v\n\n"; - } - elsif ($line && - length($line)+length($v)-rindex($line, "\n") >= 70) - { - $line .= ",\n$sp$v"; - } - else { - $line = $line ? "$line, $v" : "$key: $v"; - } - } - - $line = "$key:" unless @values; - if ($line) { - if ($line =~ /\n/) { - if (@lines && $lines[-1] !~ /\n\n$/) { - $lines[-1] .= "\n"; - } - $line .= "\n"; - } - push @lines, "$line\n"; - } - } - - $text .= join "", @lines; - } - else { - chomp $text; - } - push @text, $text; - } - - return join "\n--\n\n", @text; -} - -# Configuration. -# -------------- - -# Returns configuration information from the environment. -sub config_from_env { - my %env; - - foreach my $k ("DEBUG", "USER", "PASSWD", "SERVER") { - if (exists $ENV{"RT$k"}) { - $env{lc $k} = $ENV{"RT$k"}; - } - } - - return %env; -} - -# Finds a suitable configuration file and returns information from it. -sub config_from_file { - my ($rc) = @_; - - if ($rc =~ m#^/#) { - # We'll use an absolute path if we were given one. - return parse_config_file($rc); - } - else { - # Otherwise we'll use the first file we can find in the current - # directory, or in one of its (increasingly distant) ancestors. - - my @dirs = split /\//, cwd; - while (@dirs) { - my $file = join('/', @dirs, $rc); - if (-r $file) { - return parse_config_file($file); - } - - # Remove the last directory component each time. - pop @dirs; - } - - # Still nothing? We'll fall back to some likely defaults. - for ("$HOME/$rc", "/etc/rt.conf") { - return parse_config_file($_) if (-r $_); - } - } - - return (); -} - -# Makes a hash of the specified configuration file. -sub parse_config_file { - my %cfg; - my ($file) = @_; - - open(CFG, $file) && do { - while (<CFG>) { - chomp; - next if (/^#/ || /^\s*$/); - - if (/^(user|passwd|server)\s+([^ ]+)$/) { - $cfg{$1} = $2; - } - else { - die "rt: $file:$.: unknown configuration directive.\n"; - } - } - }; - - return %cfg; -} - -# Helper functions. -# ----------------- - -sub whine { - my $sub = (caller(1))[3]; - $sub =~ s/^main:://; - warn "rt: $sub: @_\n"; - return; -} - -sub read_passwd { - eval 'require Term::ReadKey'; - if ($@) { - die "No password specified (and Term::ReadKey not installed).\n"; - } - - print "Password: "; - Term::ReadKey::ReadMode('noecho'); - chomp(my $passwd = Term::ReadKey::ReadLine(0)); - Term::ReadKey::ReadMode('restore'); - print "\n"; - - return $passwd; -} - -sub vi { - my ($text) = @_; - my $file = "/tmp/rt.form.$$"; - my $editor = $ENV{EDITOR} || $ENV{VISUAL} || "vi"; - - local *F; - local $/ = undef; - - open(F, ">$file") || die "$file: $!\n"; print F $text; close(F); - system($editor, $file) && die "Couldn't run $editor.\n"; - open(F, $file) || die "$file: $!\n"; $text = <F>; close(F); - unlink($file); - - return $text; -} - -# Add a value to a (possibly multi-valued) hash key. -sub vpush { - my ($hash, $key, $val) = @_; - my @val = ref $val eq 'ARRAY' ? @$val : $val; - - if (exists $hash->{$key}) { - unless (ref $hash->{$key} eq 'ARRAY') { - my @v = $hash->{$key} ne '' ? $hash->{$key} : (); - $hash->{$key} = \@v; - } - push @{ $hash->{$key} }, @val; - } - else { - $hash->{$key} = $val; - } -} - -# "Normalise" a hash key that's known to be multi-valued. -sub vsplit { - my ($val) = @_; - my ($word, @words); - my @values = ref $val eq 'ARRAY' ? @$val : $val; - - foreach my $line (map {split /\n/} @values) { - # XXX: This should become a real parser, à la Text::ParseWords. - $line =~ s/^\s+//; - $line =~ s/\s+$//; - push @words, split /\s*,\s*/, $line; - } - - return \@words; -} - -sub expand_list { - my ($list) = @_; - my ($elt, @elts, %elts); - - foreach $elt (split /,/, $list) { - if ($elt =~ /^(\d+)-(\d+)$/) { push @elts, ($1..$2) } - else { push @elts, $elt } - } - - @elts{@elts}=(); - return sort {$a<=>$b} keys %elts; -} - -sub get_type_argument { - my $type; - - if (@ARGV) { - $type = shift @ARGV; - unless ($type =~ /^[A-Za-z0-9_.-]+$/) { - # We want whine to mention our caller, not us. - @_ = ("Invalid type '$type' specified."); - goto &whine; - } - } - else { - @_ = ("No type argument specified with -t."); - goto &whine; - } - - $type =~ s/s$//; # "Plural". Ugh. - return $type; -} - -sub get_var_argument { - my ($data) = @_; - - if (@ARGV) { - my $kv = shift @ARGV; - if (my ($k, $v) = $kv =~ /^($field)=(.*)$/) { - push @{ $data->{$k} }, $v; - } - else { - @_ = ("Invalid variable specification: '$kv'."); - goto &whine; - } - } - else { - @_ = ("No variable argument specified with -S."); - goto &whine; - } -} - -sub is_object_spec { - my ($spec, $type) = @_; - - $spec =~ s|^(?:$type/)?|$type/| if defined $type; - return $spec if ($spec =~ m{^$name/(?:$idlist|$labels)(?:/.*)?$}o); - return; -} - -__DATA__ - -Title: intro -Title: introduction -Text: - - ** THIS IS AN UNSUPPORTED PREVIEW RELEASE ** - ** PLEASE REPORT BUGS TO rt-bugs@fsck.com ** - - This is a command-line interface to RT 3. - - It allows you to interact with an RT server over HTTP, and offers an - interface to RT's functionality that is better-suited to automation - and integration with other tools. - - In general, each invocation of this program should specify an action - to perform on one or more objects, and any other arguments required - to complete the desired action. - - For more information: - - - rt help actions (a list of possible actions) - - rt help objects (how to specify objects) - - rt help usage (syntax information) - - - rt help config (configuration details) - - rt help examples (a few useful examples) - - rt help topics (a list of help topics) - --- - -Title: usage -Title: syntax -Text: - - Syntax: - - rt <action> [options] [arguments] - - Each invocation of this program must specify an action (e.g. "edit", - "create"), options to modify behaviour, and other arguments required - by the specified action. (For example, most actions expect a list of - numeric object IDs to act upon.) - - The details of the syntax and arguments for each action are given by - "rt help <action>". Some actions may be referred to by more than one - name ("create" is the same as "new", for example). - - Objects are identified by a type and an ID (which can be a name or a - number, depending on the type). For some actions, the object type is - implied (you can only comment on tickets); for others, the user must - specify it explicitly. See "rt help objects" for details. - - In syntax descriptions, mandatory arguments that must be replaced by - appropriate value are enclosed in <>, and optional arguments are - indicated by [] (for example, <action> and [options] above). - - For more information: - - - rt help objects (how to specify objects) - - rt help actions (a list of actions) - - rt help types (a list of object types) - --- - -Title: conf -Title: config -Title: configuration -Text: - - This program has two major sources of configuration information: its - configuration files, and the environment. - - The program looks for configuration directives in a file named .rtrc - (or $RTCONFIG; see below) in the current directory, and then in more - distant ancestors, until it reaches /. If no suitable configuration - files are found, it will also check for ~/.rtrc and /etc/rt.conf. - - Configuration directives: - - The following directives may occur, one per line: - - - server <URL> URL to RT server. - - user <username> RT username. - - passwd <passwd> RT user's password. - - Blank and #-commented lines are ignored. - - Environment variables: - - The following environment variables override any corresponding - values defined in configuration files: - - - RTUSER - - RTPASSWD - - RTSERVER - - RTDEBUG Numeric debug level. (Set to 3 for full logs.) - - RTCONFIG Specifies a name other than ".rtrc" for the - configuration file. - --- - -Title: objects -Text: - - Syntax: - - <type>/<id>[/<attributes>] - - Every object in RT has a type (e.g. "ticket", "queue") and a numeric - ID. Some types of objects can also be identified by name (like users - and queues). Furthermore, objects may have named attributes (such as - "ticket/1/history"). - - An object specification is like a path in a virtual filesystem, with - object types as top-level directories, object IDs as subdirectories, - and named attributes as further subdirectories. - - A comma-separated list of names, numeric IDs, or numeric ranges can - be used to specify more than one object of the same type. Note that - the list must be a single argument (i.e., no spaces). For example, - "user/root,1-3,5,7-10,ams" is a list of ten users; the same list - can also be written as "user/ams,root,1,2,3,5,7,8-20". - - Examples: - - ticket/1 - ticket/1/attachments - ticket/1/attachments/3 - ticket/1/attachments/3/content - ticket/1-3/links - ticket/1-3,5-7/history - - user/ams - user/ams/rights - user/ams,rai,1/rights - - For more information: - - - rt help <action> (action-specific details) - - rt help <type> (type-specific details) - --- - -Title: actions -Title: commands -Text: - - You can currently perform the following actions on all objects: - - - list (list objects matching some condition) - - show (display object details) - - edit (edit object details) - - create (create a new object) - - Each type may define actions specific to itself; these are listed in - the help item about that type. - - For more information: - - - rt help <action> (action-specific details) - - rt help types (a list of possible types) - --- - -Title: types -Text: - - You can currently operate on the following types of objects: - - - tickets - - users - - groups - - queues - - For more information: - - - rt help <type> (type-specific details) - - rt help objects (how to specify objects) - - rt help actions (a list of possible actions) - --- - -Title: ticket -Text: - - Tickets are identified by a numeric ID. - - The following generic operations may be performed upon tickets: - - - list - - show - - edit - - create - - In addition, the following ticket-specific actions exist: - - - link - - merge - - comment - - correspond - - Attributes: - - The following attributes can be used with "rt show" or "rt edit" - to retrieve or edit other information associated with tickets: - - links A ticket's relationships with others. - history All of a ticket's transactions. - history/type/<type> Only a particular type of transaction. - history/id/<id> Only the transaction of the specified id. - attachments A list of attachments. - attachments/<id> The metadata for an individual attachment. - attachments/<id>/content The content of an individual attachment. - --- - -Title: user -Title: group -Text: - - Users and groups are identified by name or numeric ID. - - The following generic operations may be performed upon them: - - - list - - show - - edit - - create - - In addition, the following type-specific actions exist: - - - grant - - revoke - - Attributes: - - The following attributes can be used with "rt show" or "rt edit" - to retrieve or edit other information associated with users and - groups: - - rights Global rights granted to this user. - rights/<queue> Queue rights for this user. - --- - -Title: queue -Text: - - Queues are identified by name or numeric ID. - - Currently, they can be subjected to the following actions: - - - show - - edit - - create - --- - -Title: logout -Text: - - Syntax: - - rt logout - - Terminates the currently established login session. You will need to - provide authentication credentials before you can continue using the - server. (See "rt help config" for details about authentication.) - --- - -Title: ls -Title: list -Title: search -Text: - - Syntax: - - rt <ls|list|search> [options] "query string" - - Displays a list of objects matching the specified conditions. - ("ls", "list", and "search" are synonyms.) - - Conditions are expressed in the SQL-like syntax used internally by - RT3. (For more information, see "rt help query".) The query string - must be supplied as one argument. - - (Right now, the server doesn't support listing anything but tickets. - Other types will be supported in future; this client will be able to - take advantage of that support without any changes.) - - Options: - - The following options control how much information is displayed - about each matching object: - - -i Numeric IDs only. (Useful for |rt edit -; see examples.) - -s Short description. - -l Longer description. - - In addition, - - -o +/-<field> Orders the returned list by the specified field. - -S var=val Submits the specified variable with the request. - -t type Specifies the type of object to look for. (The - default is "ticket".) - - Examples: - - rt ls "Priority > 5 and Status='new'" - rt ls -o +Subject "Priority > 5 and Status='new'" - rt ls -o -Created "Priority > 5 and Status='new'" - rt ls -i "Priority > 5"|rt edit - set status=resolved - rt ls -t ticket "Subject like '[PATCH]%'" - --- - -Title: show -Text: - - Syntax: - - rt show [options] <object-ids> - - Displays details of the specified objects. - - For some types, object information is further classified into named - attributes (for example, "1-3/links" is a valid ticket specification - that refers to the links for tickets 1-3). Consult "rt help <type>" - and "rt help objects" for further details. - - This command writes a set of forms representing the requested object - data to STDOUT. - - Options: - - - Read IDs from STDIN instead of the command-line. - -t type Specifies object type. - -f a,b,c Restrict the display to the specified fields. - -S var=val Submits the specified variable with the request. - - Examples: - - rt show -t ticket -f id,subject,status 1-3 - rt show ticket/3/attachments/29 - rt show ticket/3/attachments/29/content - rt show ticket/1-3/links - rt show -t user 2 - --- - -Title: new -Title: edit -Title: create -Text: - - Syntax: - - rt edit [options] <object-ids> set field=value [field=value] ... - add field=value [field=value] ... - del field=value [field=value] ... - - Edits information corresponding to the specified objects. - - If, instead of "edit", an action of "new" or "create" is specified, - then a new object is created. In this case, no numeric object IDs - may be specified, but the syntax and behaviour remain otherwise - unchanged. - - This command typically starts an editor to allow you to edit object - data in a form for submission. If you specified enough information - on the command-line, however, it will make the submission directly. - - The command line may specify field-values in three different ways. - "set" sets the named field to the given value, "add" adds a value - to a multi-valued field, and "del" deletes the corresponding value. - Each "field=value" specification must be given as a single argument. - - For some types, object information is further classified into named - attributes (for example, "1-3/links" is a valid ticket specification - that refers to the links for tickets 1-3). These attributes may also - be edited. Consult "rt help <type>" and "rt help object" for further - details. - - Options: - - - Read numeric IDs from STDIN instead of the command-line. - (Useful with rt ls ... | rt edit -; see examples below.) - -i Read a completed form from STDIN before submitting. - -o Dump the completed form to STDOUT instead of submitting. - -e Allows you to edit the form even if the command-line has - enough information to make a submission directly. - -S var=val - Submits the specified variable with the request. - -t type Specifies object type. - - Examples: - - # Interactive (starts $EDITOR with a form). - rt edit ticket/3 - rt create -t ticket - - # Non-interactive. - rt edit ticket/1-3 add cc=foo@example.com set priority=3 - rt ls -t tickets -i 'Priority > 5' | rt edit - set status=resolved - rt edit ticket/4 set priority=3 owner=bar@example.com \ - add cc=foo@example.com bcc=quux@example.net - rt create -t ticket subject='new ticket' priority=10 \ - add cc=foo@example.com - --- - -Title: comment -Title: correspond -Text: - - Syntax: - - rt <comment|correspond> [options] <ticket-id> - - Adds a comment (or correspondence) to the specified ticket (the only - difference being that comments aren't sent to the requestors.) - - This command will typically start an editor and allow you to type a - comment into a form. If, however, you specified all the necessary - information on the command line, it submits the comment directly. - - (See "rt help forms" for more information about forms.) - - Options: - - -m <text> Specify comment text. - -a <file> Attach a file to the comment. (May be used more - than once to attach multiple files.) - -c <addrs> A comma-separated list of Cc addresses. - -b <addrs> A comma-separated list of Bcc addresses. - -w <time> Specify the time spent working on this ticket. - -e Starts an editor before the submission, even if - arguments from the command line were sufficient. - - Examples: - - rt comment -t 'Not worth fixing.' -a stddisclaimer.h 23 - --- - -Title: merge -Text: - - Syntax: - - rt merge <from-id> <to-id> - - Merges the two specified tickets. - --- - -Title: link -Text: - - Syntax: - - rt link [-d] <id-A> <relationship> <id-B> - - Creates (or, with -d, deletes) a link between the specified tickets. - The relationship can (irrespective of case) be any of: - - DependsOn/DependedOnBy: A depends upon B (or vice versa). - RefersTo/ReferredToBy: A refers to B (or vice versa). - MemberOf/HasMember: A is a member of B (or vice versa). - - To view a ticket's relationships, use "rt show ticket/3/links". (See - "rt help ticket" and "rt help show".) - - Options: - - -d Deletes the specified link. - - Examples: - - rt link 2 dependson 3 - rt link -d 4 referredtoby 6 # 6 no longer refers to 4 - --- - -Title: grant -Title: revoke -Text: - --- - -Title: query -Text: - - RT3 uses an SQL-like syntax to specify object selection constraints. - See the <RT:...> documentation for details. - - (XXX: I'm going to have to write it, aren't I?) - --- - -Title: form -Title: forms -Text: - - This program uses RFC822 header-style forms to represent object data - in a form that's suitable for processing both by humans and scripts. - - A form is a set of (field, value) specifications, with some initial - commented text and interspersed blank lines allowed for convenience. - Field names may appear more than once in a form; a comma-separated - list of multiple field values may also be specified directly. - - Field values can be wrapped as in RFC822, with leading whitespace. - The longest sequence of leading whitespace common to all the lines - is removed (preserving further indentation). There is no limit on - the length of a value. - - Multiple forms are separated by a line containing only "--\n". - - (XXX: A more detailed specification will be provided soon. For now, - the server-side syntax checking will suffice.) - --- - -Title: topics -Text: - - Use "rt help <topic>" for help on any of the following subjects: - - - tickets, users, groups, queues. - - show, edit, ls/list/search, new/create. - - - query (search query syntax) - - forms (form specification) - - - objects (how to specify objects) - - types (a list of object types) - - actions/commands (a list of actions) - - usage/syntax (syntax details) - - conf/config/configuration (configuration details) - - examples (a few useful examples) - --- - -Title: example -Title: examples -Text: - - This section will be filled in with useful examples, once it becomes - more clear what examples may be useful. - - For the moment, please consult examples provided with each action. - --- diff --git a/rt/bin/rt-mailgate b/rt/bin/rt-mailgate index 8af800227..b30443638 100755 --- a/rt/bin/rt-mailgate +++ b/rt/bin/rt-mailgate @@ -1,26 +1,26 @@ #!/usr/bin/perl -w # BEGIN LICENSE BLOCK -# +# # Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com> -# +# # (Except where explictly superceded by other copyright notices) -# +# # 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. -# +# # Unless otherwise specified, all modifications, corrections or # extensions to this work which alter its source code become the # property of Best Practical Solutions, LLC when submitted for # inclusion in the work. -# -# +# +# # END LICENSE BLOCK =head1 NAME @@ -31,25 +31,10 @@ rt-mailgate - Mail interface to RT3. use RT::I18N; -# Make sure that when we call the mailgate wrong, it tempfails - -ok(open(MAIL, "|/opt/rt3/bin/rt-mailgate --url http://bad.address"), "Opened the mailgate - The error below is expected - $@"); -print MAIL <<EOF; -From: root\@localhost -To: rt\@example.com -Subject: This is a test of new ticket creation - -Foob! -EOF -close (MAIL); - -# Check the return value -is ( $? >> 8, 75, "The error message above is expected The mail gateway exited with a failure. yay"); - # {{{ Test new ticket creation by root who is privileged and superuser -ok(open(MAIL, "|/opt/rt3/bin/rt-mailgate --url http://localhost".$RT::WebPath."/ --queue general --action correspond"), "Opened the mailgate - $@"); +ok(open(MAIL, "|/opt/rt3/bin/rt-mailgate --url http://localhost/ --queue general --action correspond"), "Opened the mailgate - $@"); print MAIL <<EOF; From: root\@localhost To: rt\@example.com @@ -60,9 +45,6 @@ Foob! EOF close (MAIL); -#Check the return value -is ($? >> 8, 0, "The mail gateway exited normally. yay"); - use RT::Tickets; my $tickets = RT::Tickets->new($RT::SystemUser); $tickets->OrderBy(FIELD => 'id', ORDER => 'DESC'); @@ -77,7 +59,7 @@ ok ($tick->Subject eq 'This is a test of new ticket creation', "Created the tick # {{{This is a test of new ticket creation as an unknown user -ok(open(MAIL, "|/opt/rt3/bin/rt-mailgate --url http://localhost".$RT::WebPath."/ --queue general --action correspond"), "Opened the mailgate - $@"); +ok(open(MAIL, "|/opt/rt3/bin/rt-mailgate --url http://localhost/ --queue general --action correspond"), "Opened the mailgate - $@"); print MAIL <<EOF; From: doesnotexist\@example.com To: rt\@example.com @@ -87,8 +69,6 @@ Blah! Foob! EOF close (MAIL); -#Check the return value -is ($? >> 8, 0, "The mail gateway exited normally. yay"); $tickets = RT::Tickets->new($RT::SystemUser); $tickets->OrderBy(FIELD => 'id', ORDER => 'DESC'); @@ -114,7 +94,7 @@ ok ($val, "Granted everybody the right to create tickets - $msg"); sleep(60); # gotta sleep so the remote process' ACL cache times out -ok(open(MAIL, "|/opt/rt3/bin/rt-mailgate --url http://localhost".$RT::WebPath."/ --queue general --action correspond"), "Opened the mailgate - $@"); +ok(open(MAIL, "|/opt/rt3/bin/rt-mailgate --url http://localhost/ --queue general --action correspond"), "Opened the mailgate - $@"); print MAIL <<EOF; From: doesnotexist\@example.com To: rt\@example.com @@ -124,8 +104,6 @@ Blah! Foob! EOF close (MAIL); -#Check the return value -is ($? >> 8, 0, "The mail gateway exited normally. yay"); $tickets = RT::Tickets->new($RT::SystemUser); @@ -148,7 +126,7 @@ ok( $u->Id != 0, " user does not exist and was created by ticket submission"); #ok ($val, "Granted everybody the right to create tickets - $msg"); #sleep(60); # gotta sleep so the remote process' ACL cache times out -ok(open(MAIL, "|/opt/rt3/bin/rt-mailgate --url http://localhost".$RT::WebPath."/ --queue general --action correspond"), "Opened the mailgate - $@"); +ok(open(MAIL, "|/opt/rt3/bin/rt-mailgate --url http://localhost/ --queue general --action correspond"), "Opened the mailgate - $@"); print MAIL <<EOF; From: doesnotexist-2\@example.com To: rt\@example.com @@ -158,8 +136,6 @@ Blah! Foob! EOF close (MAIL); -#Check the return value -is ($? >> 8, 0, "The mail gateway exited normally. yay"); $u = RT::User->new($RT::SystemUser); $u->Load('doesnotexist-2@example.com'); @@ -172,7 +148,7 @@ ok( $u->Id == 0, " user does not exist and was not created by ticket corresponde ok ($val, "Granted everybody the right to reply to tickets - $msg"); sleep(60); # gotta sleep so the remote process' ACL cache times out -ok(open(MAIL, "|/opt/rt3/bin/rt-mailgate --url http://localhost".$RT::WebPath."/ --queue general --action correspond"), "Opened the mailgate - $@"); +ok(open(MAIL, "|/opt/rt3/bin/rt-mailgate --url http://localhost/ --queue general --action correspond"), "Opened the mailgate - $@"); print MAIL <<EOF; From: doesnotexist-2\@example.com To: rt\@example.com @@ -182,8 +158,6 @@ Blah! Foob! EOF close (MAIL); -#Check the return value -is ($? >> 8, 0, "The mail gateway exited normally. yay"); $u = RT::User->new($RT::SystemUser); @@ -199,7 +173,7 @@ ok( $u->Id != 0, " user exists and was created by ticket correspondence submissi #ok ($val, "Granted everybody the right to create tickets - $msg"); #sleep(60); # gotta sleep so the remote process' ACL cache times out -ok(open(MAIL, "|/opt/rt3/bin/rt-mailgate --url http://localhost".$RT::WebPath."/ --queue general --action comment"), "Opened the mailgate - $@"); +ok(open(MAIL, "|/opt/rt3/bin/rt-mailgate --url http://localhost/ --queue general --action comment"), "Opened the mailgate - $@"); print MAIL <<EOF; From: doesnotexist-3\@example.com To: rt\@example.com @@ -210,9 +184,6 @@ Foob! EOF close (MAIL); -#Check the return value -is ($? >> 8, 0, "The mail gateway exited normally. yay"); - $u = RT::User->new($RT::SystemUser); $u->Load('doesnotexist-3@example.com'); ok( $u->Id == 0, " user does not exist and was not created by ticket comment submission"); @@ -225,7 +196,7 @@ ok( $u->Id == 0, " user does not exist and was not created by ticket comment sub ok ($val, "Granted everybody the right to reply to tickets - $msg"); sleep(60); # gotta sleep so the remote process' ACL cache times out -ok(open(MAIL, "|/opt/rt3/bin/rt-mailgate --url http://localhost".$RT::WebPath."/ --queue general --action comment"), "Opened the mailgate - $@"); +ok(open(MAIL, "|/opt/rt3/bin/rt-mailgate --url http://localhost/ --queue general --action comment"), "Opened the mailgate - $@"); print MAIL <<EOF; From: doesnotexist-3\@example.com To: rt\@example.com @@ -236,8 +207,6 @@ Foob! EOF close (MAIL); -#Check the return value -is ($? >> 8, 0, "The mail gateway exited normally. yay"); $u = RT::User->new($RT::SystemUser); $u->Load('doesnotexist-3@example.com'); @@ -258,20 +227,17 @@ my $entity = MIME::Entity->build( From => 'root@localhost', Data => ['This is a test of a binary attachment']); # currently in lib/t/autogen -$entity->attach(Path => '/opt/rt3/share/html/NoAuth/images/spacer.gif', +$entity->attach(Path => '../../../html/NoAuth/images/spacer.gif', Type => 'image/gif', Encoding => 'base64'); # Create a ticket with a binary attachment -ok(open(MAIL, "|/opt/rt3/bin/rt-mailgate --url http://localhost".$RT::WebPath."/ --queue general --action correspond"), "Opened the mailgate - $@"); +ok(open(MAIL, "|/opt/rt3/bin/rt-mailgate --url http://localhost/ --queue general --action correspond"), "Opened the mailgate - $@"); $entity->print(\*MAIL); close (MAIL); -#Check the return value -is ($? >> 8, 0, "The mail gateway exited normally. yay"); - my $tickets = RT::Tickets->new($RT::SystemUser); $tickets->OrderBy(FIELD => 'id', ORDER => 'DESC'); $tickets->Limit(FIELD => 'id', OPERATOR => '>', VALUE => '0'); @@ -307,7 +273,7 @@ use LWP::UserAgent; # Grab the binary attachment via the web ui my $ua = LWP::UserAgent->new(); -my $full_url = "http://localhost".$RT::WebPath."/Ticket/Attachment/".$attachment->TransactionId."/".$attachment->id."/spacer.gif?&user=root&pass=password"; +my $full_url = "http://localhost/Ticket/Attachment/".$attachment->TransactionId."/".$attachment->id."/spacer.gif?&user=root&pass=password"; my $r = $ua->get( $full_url); @@ -320,7 +286,7 @@ is($file, $r->content, 'The attachment isn\'t screwed up in download'); # {{{ Simple I18N testing -ok(open(MAIL, "|/opt/rt3/bin/rt-mailgate --url http://localhost".$RT::WebPath."/ --queue general --action correspond"), "Opened the mailgate - $@"); +ok(open(MAIL, "|/opt/rt3/bin/rt-mailgate --url http://localhost/ --queue general --action correspond"), "Opened the mailgate - $@"); print MAIL <<EOF; From: root\@localhost @@ -335,9 +301,6 @@ bye EOF close (MAIL); -#Check the return value -is ($? >> 8, 0, "The mail gateway exited normally. yay"); - my $unitickets = RT::Tickets->new($RT::SystemUser); $unitickets->OrderBy(FIELD => 'id', ORDER => 'DESC'); $unitickets->Limit(FIELD => 'id', OPERATOR => '>', VALUE => '0'); @@ -354,7 +317,7 @@ is ($unitick->Transactions->First->Content, $unitick->Transactions->First->Attac ok($unitick->Transactions->First->Attachments->First->Content =~ /$unistring/i, $unitick->Id." appears to be unicode ". $unitick->Transactions->First->Attachments->First->Id); # supposedly I18N fails on the second message sent in. -ok(open(MAIL, "|/opt/rt3/bin/rt-mailgate --url http://localhost".$RT::WebPath."/ --queue general --action correspond"), "Opened the mailgate - $@"); +ok(open(MAIL, "|/opt/rt3/bin/rt-mailgate --url http://localhost/ --queue general --action correspond"), "Opened the mailgate - $@"); print MAIL <<EOF; From: root\@localhost @@ -369,9 +332,6 @@ bye EOF close (MAIL); -#Check the return value -is ($? >> 8, 0, "The mail gateway exited normally. yay"); - my $tickets2 = RT::Tickets->new($RT::SystemUser); $tickets2->OrderBy(FIELD => 'id', ORDER => 'DESC'); $tickets2->Limit(FIELD => 'id', OPERATOR => '>', VALUE => '0'); @@ -407,7 +367,7 @@ use LWP::UserAgent; use constant EX_TEMPFAIL => 75; my %opts; -GetOptions( \%opts, "queue=s", "action=s", "url=s", "jar=s", "help", "debug", "extension=s", "timeout=i" ); +GetOptions( \%opts, "queue=s", "action=s", "url=s", "jar=s", "help", "debug", "extension=s" ); if ( $opts{help} ) { require Pod::Usage; @@ -421,18 +381,17 @@ for (qw(url)) { } undef $/; +my $message = <>; my $ua = LWP::UserAgent->new(); $ua->cookie_jar( { file => $opts{jar} } ); my %args = ( queue => $opts{queue}, action => $opts{action}, + message => $message, SessionType => 'REST', # Surpress login box ); -# Read the message in from STDIN -$args{'message'} = <>; - if ($opts{'extension'}) { $args{$opts{'extension'}} = $ENV{'EXTENSION'}; @@ -445,7 +404,6 @@ warn "Connecting to $full_url" if $opts{'debug'}; -$ua->timeout(exists($opts{'timeout'}) ? $opts{'timeout'} : 180); my $r = $ua->post( $full_url, {%args} ); check_failure($r); @@ -456,7 +414,7 @@ if ( $content !~ /^(ok|not ok)/ ) { # It's not the server's fault if the mail is bogus. We just want to know that # *something* came out of the server. - warn <<EOF; + die <<EOF RT server error. The RT server which handled your email did not behave as expected. It @@ -465,13 +423,8 @@ said: $content EOF -exit EX_TEMPFAIL; - } -exit; - - sub check_failure { my $r = shift; return if $r->is_success(); @@ -502,11 +455,7 @@ Usual invocation (from MTA): rt-mailgate --action (correspond|comment) --queue queuename --url http://your.rt.server/ - [ --debug ] - [ --extension (queue|action|ticket) ] - [ --timeout seconds ] - - + [ --extension (queue|action|ticket) See C<man rt-mailgate> for more. @@ -537,16 +486,6 @@ submitted to will be set to the value of $EXTENSION. By specifying is related to. "action" will allow the user to specify either "comment" or "correspond" in the address extension. -=item C<--debug> OPTIONAL - -Print debugging output to standard error - - -=item C<--timeout> OPTIONAL - -Configure the timeout for posting the message to the web server. The -default timeout is 3 minutes (180 seconds). - =head1 DESCRIPTION diff --git a/rt/bin/webmux.pl b/rt/bin/webmux.pl index 96e7ebf8d..21cb83f5e 100755 --- a/rt/bin/webmux.pl +++ b/rt/bin/webmux.pl @@ -31,7 +31,6 @@ BEGIN { $ENV{'SHELL'} = '/bin/sh' if defined $ENV{'SHELL'}; $ENV{'ENV'} = '' if defined $ENV{'ENV'}; $ENV{'IFS'} = '' if defined $ENV{'IFS'}; - } use lib ("/opt/rt3/local/lib", "/opt/rt3/lib"); @@ -43,17 +42,6 @@ use CGI qw(-private_tempfiles); #bring this in before mason, to make sure we #set private_tempfiles BEGIN { - if ($mod_perl::VERSION >= 1.9908) { - require Apache::RequestUtil; - no warnings 'redefine'; - my $sub = *Apache::request{CODE}; - *Apache::request = sub { - my $r; - eval { $r = $sub->('Apache'); }; - # warn $@ if $@; - return $r; - }; - } if ($CGI::MOD_PERL) { require HTML::Mason::ApacheHandler; } @@ -116,32 +104,21 @@ if ( $CGI::MOD_PERL) { unless ( ( -d _ ) and ( -r _ ) and ( -w _ ) ); } -my $ah = &RT::Interface::Web::NewApacheHandler(@RT::MasonParameters) if $CGI::MOD_PERL; +my $ah = &RT::Interface::Web::NewApacheHandler() if $CGI::MOD_PERL; sub handler { ($r) = @_; - local $SIG{__WARN__}; - local $SIG{__DIE__}; - RT::Init(); # We don't need to handle non-text items return -1 if defined( $r->content_type ) && $r->content_type !~ m|^text/|io; my %session; - my $status; - eval { $status = $ah->handle_request($r) }; - if ($@) { - $RT::Logger->crit($@); - } - + my $status = $ah->handle_request($r); undef (%session); - if ($RT::Handle->TransactionDepth) { - $RT::Handle->ForceRollback; - $RT::Logger->crit("Transaction not committed. Usually indicates a software fault. Data loss may have occurred") ; - } + $RT::Logger->crit("Transaction not committed. Usually indicates a software fault. Data loss may have occurred") if $RT::Handle->TransactionDepth; return $status; } diff --git a/rt/config.layout b/rt/config.layout.in index 23a7775d6..a08f48948 100644 --- a/rt/config.layout +++ b/rt/config.layout.in @@ -102,3 +102,26 @@ customlexdir: ${customdir}/po customlibdir: ${customdir}/lib </Layout> + +<Layout Freeside> + prefix: /opt/rt3 + exec_prefix: ${prefix} + bindir: ${exec_prefix}/bin + sbindir: ${exec_prefix}/sbin + sysconfdir: ${prefix}/etc + mandir: ${prefix}/man + libdir: ${prefix}/lib + datadir: ${prefix}/share + htmldir: %%%FREESIDE_DOCUMENT_ROOT%%%/rt + manualdir: ${datadir}/doc + localstatedir: ${prefix}/var + logfiledir: ${localstatedir}/log + masonstatedir: %%%MASONDATA%%% + sessionstatedir: ${localstatedir}/session_data + customdir: ${prefix}/local + custometcdir: ${customdir}/etc + customhtmldir: ${customdir}/html + customlexdir: ${customdir}/po + customlibdir: ${customdir}/lib +</Layout> + diff --git a/rt/etc/RT_SiteConfig.pm b/rt/etc/RT_SiteConfig.pm index 0afc6045c..572a2baf6 100644 --- a/rt/etc/RT_SiteConfig.pm +++ b/rt/etc/RT_SiteConfig.pm @@ -1 +1,13 @@ +$RT::rtname = '%%%RT_DOMAIN%%%'; +$RT::Organization = '%%%RT_DOMAIN%%%'; + +$RT::Timezone = '%%%RT_TIMEZONE%%%'; + +$RT::WebBaseURL = ''; +$RT::WebPath = '/freeside/rt'; + +$RT::WebExternalAuth = 1; +$RT::WebFallbackToInternal = 1; #no +$RT::WebExternalAuto = 1; + 1; diff --git a/rt/etc/acl.Oracle b/rt/etc/acl.Oracle index ac29215c2..c8667c031 100644 --- a/rt/etc/acl.Oracle +++ b/rt/etc/acl.Oracle @@ -1,10 +1,10 @@ sub acl { return ( -"CREATE USER ${RT::DatabaseUser} identified by ${RT::DatabasePassword} ". -"default tablespace USERS " . -"temporary tablespace TEMP " . -"quota unlimited on USERS" , -"grant connect, resource to ${RT::DatabaseUser}" -); +"CREATE USER ${RT::DatabaseUser} identified by ${RT::DatabasePassword}". +"temporary tablespace TEMP" . +"default tablespace USERS" . +"quota unlimited on USERS;" , +"grant connect, resource to ${RT::DatabaseUser};", +"exit;"); } 1; diff --git a/rt/etc/acl.mysql b/rt/etc/acl.mysql index 5bd883472..0ecaa3b15 100755 --- a/rt/etc/acl.mysql +++ b/rt/etc/acl.mysql @@ -3,6 +3,6 @@ return ( "USE mysql;", "DELETE FROM user WHERE user = '${RT::DatabaseUser}';", "DELETE FROM db where db = '${RT::DatabaseName}';", -"GRANT SELECT,INSERT,CREATE,INDEX,UPDATE,DELETE ON ${RT::DatabaseName}.* TO ${RT::DatabaseUser}\@'${RT::DatabaseRTHost}' IDENTIFIED BY '${RT::DatabasePassword}';"); +"GRANT SELECT,INSERT,CREATE,INDEX,UPDATE,DELETE ON ${RT::DatabaseName}.* TO ${RT::DatabaseUser}\@${RT::DatabaseRTHost} IDENTIFIED BY '${RT::DatabasePassword}';"); } 1; diff --git a/rt/etc/schema.Oracle b/rt/etc/schema.Oracle deleted file mode 100644 index 95cfda2fd..000000000 --- a/rt/etc/schema.Oracle +++ /dev/null @@ -1,354 +0,0 @@ -CREATE SEQUENCE ATTACHMENTS_seq; -CREATE TABLE Attachments ( - id NUMBER(11,0) - CONSTRAINT Attachments_Key PRIMARY KEY, - TransactionId NUMBER(11,0) NOT NULL, - Parent NUMBER(11,0) DEFAULT 0 NOT NULL, - MessageId VARCHAR2(160), - Subject VARCHAR2(255), - Filename VARCHAR2(255), - ContentType VARCHAR2(80), - ContentEncoding VARCHAR2(80), - Content CLOB, - Headers CLOB, - Creator NUMBER(11,0) DEFAULT 0 NOT NULL, - Created DATE -); -CREATE INDEX Attachments2 ON Attachments (TransactionId); -CREATE INDEX Attachments3 ON Attachments (Parent, TransactionId); - - -CREATE SEQUENCE QUEUES_seq; -CREATE TABLE Queues ( - id NUMBER(11,0) - CONSTRAINT Queues_Key PRIMARY KEY, - Name VARCHAR2(200) CONSTRAINT Queues_Name_Unique UNIQUE NOT NULL, - Description VARCHAR2(255), - CorrespondAddress VARCHAR2(120), - CommentAddress VARCHAR2(120), - InitialPriority NUMBER(11,0) DEFAULT 0 NOT NULL, - FinalPriority NUMBER(11,0) DEFAULT 0 NOT NULL, - DefaultDueIn NUMBER(11,0) DEFAULT 0 NOT NULL, - Creator NUMBER(11,0) DEFAULT 0 NOT NULL, - Created DATE, - LastUpdatedBy NUMBER(11,0) DEFAULT 0 NOT NULL, - LastUpdated DATE, - Disabled NUMBER(11,0) DEFAULT 0 NOT NULL -); - CREATE INDEX Queues1 ON Queues (lower(Name)); -CREATE INDEX Queues2 ON Queues (Disabled); - - -CREATE SEQUENCE LINKS_seq; -CREATE TABLE Links ( - id NUMBER(11,0) - CONSTRAINT Links_Key PRIMARY KEY, - Base VARCHAR2(240), - Target VARCHAR2(240), - Type VARCHAR2(20) NOT NULL, - LocalTarget NUMBER(11,0) DEFAULT 0 NOT NULL, - LocalBase NUMBER(11,0) DEFAULT 0 NOT NULL, - LastUpdatedBy NUMBER(11,0) DEFAULT 0 NOT NULL, - LastUpdated DATE, - Creator NUMBER(11,0) DEFAULT 0 NOT NULL, - Created DATE -); -CREATE UNIQUE INDEX Links1 ON Links (Base, Target, Type); -CREATE INDEX Links2 ON Links (Base, Type); -CREATE INDEX Links3 ON Links (Target, Type); -CREATE INDEX Links4 ON Links(Type,LocalBase); - - -CREATE SEQUENCE PRINCIPALS_seq; -CREATE TABLE Principals ( - id NUMBER(11,0) - CONSTRAINT Principals_Key PRIMARY KEY, - PrincipalType VARCHAR2(16), - ObjectId NUMBER(11,0), - Disabled NUMBER(11,0) DEFAULT 0 NOT NULL -); -CREATE UNIQUE INDEX Principals2 ON Principals (ObjectId); - - -CREATE SEQUENCE GROUPS_seq; -CREATE TABLE Groups ( - id NUMBER(11,0) - CONSTRAINT Groups_Key PRIMARY KEY, - Name VARCHAR2(200), - Description VARCHAR2(255), - Domain VARCHAR2(64), - Type VARCHAR2(64), - Instance NUMBER(11,0) DEFAULT 0 -- NOT NULL --- Instance VARCHAR2(64) -); -CREATE INDEX Groups1 ON Groups (lower( Domain), Instance, lower(Type), id); -CREATE INDEX Groups2 ON Groups (lower(Type), Instance, lower(Domain)); - - -CREATE SEQUENCE SCRIPCONDITIONS_seq; -CREATE TABLE ScripConditions ( - id NUMBER(11, 0) - CONSTRAINT ScripConditions_Key PRIMARY KEY, - Name VARCHAR2(200), - Description VARCHAR2(255), - ExecModule VARCHAR2(60), - Argument VARCHAR2(255), - ApplicableTransTypes VARCHAR2(60), - Creator NUMBER(11,0) DEFAULT 0 NOT NULL, - Created DATE, - LastUpdatedBy NUMBER(11,0) DEFAULT 0 NOT NULL, - LastUpdated DATE -); - - -CREATE SEQUENCE TRANSACTIONS_seq; -CREATE TABLE Transactions ( - id NUMBER(11,0) - CONSTRAINT Transactions_Key PRIMARY KEY, - EffectiveTicket NUMBER(11,0) DEFAULT 0 NOT NULL, - Ticket NUMBER(11,0) DEFAULT 0 NOT NULL, - TimeTaken NUMBER(11,0) DEFAULT 0 NOT NULL, - Type VARCHAR2(20), - Field VARCHAR2(40), - OldValue VARCHAR2(255), - NewValue VARCHAR2(255), - Data VARCHAR2(255), - Creator NUMBER(11,0) DEFAULT 0 NOT NULL, - Created DATE -); -CREATE INDEX Transactions1 ON Transactions (Ticket); -CREATE INDEX Transactions2 ON Transactions (EffectiveTicket); - - -CREATE SEQUENCE SCRIPS_seq; -CREATE TABLE Scrips ( - id NUMBER(11,0) - CONSTRAINT Scrips_Key PRIMARY KEY, - Description VARCHAR2(255), - ScripCondition NUMBER(11,0) DEFAULT 0 NOT NULL, - ScripAction NUMBER(11,0) DEFAULT 0 NOT NULL, - ConditionRules CLOB, - ActionRules CLOB, - CustomIsApplicableCode CLOB, - CustomPrepareCode CLOB, - CustomCommitCode CLOB, - Stage VARCHAR2(32), - Queue NUMBER(11,0) DEFAULT 0 NOT NULL, - Template NUMBER(11,0) DEFAULT 0 NOT NULL, - Creator NUMBER(11,0) DEFAULT 0 NOT NULL, - Created DATE, - LastUpdatedBy NUMBER(11,0) DEFAULT 0 NOT NULL, - LastUpdated DATE -); - - -CREATE SEQUENCE ACL_seq; -CREATE TABLE ACL ( - id NUMBER(11,0) - CONSTRAINT ACL_Key PRIMARY KEY, - PrincipalType VARCHAR2(25) NOT NULL, - PrincipalId NUMBER(11,0) NOT NULL, - RightName VARCHAR2(25) NOT NULL, - ObjectType VARCHAR2(25) NOT NULL, - ObjectId NUMBER(11,0) DEFAULT 0 NOT NULL, - DelegatedBy NUMBER(11,0) DEFAULT 0 NOT NULL, - DelegatedFrom NUMBER(11,0) DEFAULT 0 NOT NULL -); -CREATE INDEX ACL1 ON ACL(RightName, ObjectType, ObjectId, PrincipalType, PrincipalId); - - -CREATE SEQUENCE GROUPMEMBERS_seq; -CREATE TABLE GroupMembers ( - id NUMBER(11,0) - CONSTRAINT GroupMembers_Key PRIMARY KEY, - GroupId NUMBER(11,0) DEFAULT 0 NOT NULL, - MemberId NUMBER(11,0) DEFAULT 0 NOT NULL -); -CREATE UNIQUE INDEX GroupMembers1 ON GroupMembers (GroupId, MemberId); - - -CREATE SEQUENCE CachedGroupMembers_seq; -CREATE TABLE CachedGroupMembers ( - id NUMBER(11,0) - CONSTRAINT CachedGroupMembers_Key PRIMARY KEY, - GroupId NUMBER(11,0), - MemberId NUMBER(11,0), - Via NUMBER(11,0), - ImmediateParentId NUMBER(11,0), - Disabled NUMBER(11,0) DEFAULT 0 NOT NULL -); -CREATE INDEX DisGrouMem ON CachedGroupMembers (GroupId, MemberId, Disabled); -CREATE INDEX GrouMem ON CachedGroupMembers (GroupId, MemberId); - - -CREATE SEQUENCE USERS_seq; -CREATE TABLE Users ( - id NUMBER(11,0) - CONSTRAINT Users_Key PRIMARY KEY, - Name VARCHAR2(200) CONSTRAINT Users_Name_Unique - unique NOT NULL, - Password VARCHAR2(40), - Comments CLOB, - Signature CLOB, - EmailAddress VARCHAR2(120), - FreeFormContactInfo CLOB, - Organization VARCHAR2(200), - RealName VARCHAR2(120), - NickName VARCHAR2(16), - Lang VARCHAR2(16), - EmailEncoding VARCHAR2(16), - WebEncoding VARCHAR2(16), - ExternalContactInfoId VARCHAR2(100), - ContactInfoSystem VARCHAR2(30), - ExternalAuthId VARCHAR2(100), - AuthSystem VARCHAR2(30), - Gecos VARCHAR2(16), - HomePhone VARCHAR2(30), - WorkPhone VARCHAR2(30), - MobilePhone VARCHAR2(30), - PagerPhone VARCHAR2(30), - Address1 VARCHAR2(200), - Address2 VARCHAR2(200), - City VARCHAR2(100), - State VARCHAR2(100), - Zip VARCHAR2(16), - Country VARCHAR2(50), - Timezone VARCHAR2(50), - PGPKey CLOB, - Creator NUMBER(11,0) DEFAULT 0 NOT NULL, - Created DATE, - LastUpdatedBy NUMBER(11,0) DEFAULT 0 NOT NULL, - LastUpdated DATE -); --- CREATE UNIQUE INDEX Users1 ON Users (Name); - -CREATE INDEX Users2 ON Users( LOWER(name)); -CREATE INDEX Users4 ON Users (lower(EmailAddress)); - - -CREATE SEQUENCE TICKETS_seq; -CREATE TABLE Tickets ( - id NUMBER(11, 0) - CONSTRAINT Tickets_Key PRIMARY KEY, - EffectiveId NUMBER(11,0) DEFAULT 0 NOT NULL, - Queue NUMBER(11,0) DEFAULT 0 NOT NULL, - Type VARCHAR2(16), - IssueStatement NUMBER(11,0) DEFAULT 0 NOT NULL, - Resolution NUMBER(11,0) DEFAULT 0 NOT NULL, - Owner NUMBER(11,0) DEFAULT 0 NOT NULL, - Subject VARCHAR2(200) DEFAULT '[no subject]', - InitialPriority NUMBER(11,0) DEFAULT 0 NOT NULL, - FinalPriority NUMBER(11,0) DEFAULT 0 NOT NULL, - Priority NUMBER(11,0) DEFAULT 0 NOT NULL, - TimeEstimated NUMBER(11,0) DEFAULT 0 NOT NULL, - TimeWorked NUMBER(11,0) DEFAULT 0 NOT NULL, - Status VARCHAR2(10), - TimeLeft NUMBER(11,0) DEFAULT 0 NOT NULL, - Told DATE, - Starts DATE, - Started DATE, - Due DATE, - Resolved DATE, - LastUpdatedBy NUMBER(11,0) DEFAULT 0 NOT NULL, - LastUpdated DATE, - Creator NUMBER(11,0) DEFAULT 0 NOT NULL, - Created DATE, - Disabled NUMBER(11,0) DEFAULT 0 NOT NULL -); -CREATE INDEX Tickets1 ON Tickets (Queue, Status); -CREATE INDEX Tickets2 ON Tickets (Owner); -CREATE INDEX Tickets4 ON Tickets (id, Status); -CREATE INDEX Tickets5 ON Tickets (id, EffectiveId); -CREATE INDEX Tickets6 ON Tickets (EffectiveId, Type); - - -CREATE SEQUENCE SCRIPACTIONS_seq; -CREATE TABLE ScripActions ( - id NUMBER(11,0) - CONSTRAINT ScripActions_Key PRIMARY KEY, - Name VARCHAR2(200), - Description VARCHAR2(255), - ExecModule VARCHAR2(60), - Argument VARCHAR2(255), - Creator NUMBER(11,0) DEFAULT 0 NOT NULL, - Created DATE, - LastUpdatedBy NUMBER(11,0) DEFAULT 0 NOT NULL, - LastUpdated DATE -); - - -CREATE SEQUENCE TEMPLATES_seq; -CREATE TABLE Templates ( - id NUMBER(11,0) - CONSTRAINT Templates_Key PRIMARY KEY, - Queue NUMBER(11,0) DEFAULT 0 NOT NULL, - Name VARCHAR2(200) NOT NULL, - Description VARCHAR2(255), - Type VARCHAR2(16), - Language VARCHAR2(16), - TranslationOf NUMBER(11,0) DEFAULT 0 NOT NULL, - Content CLOB, - LastUpdated DATE, - LastUpdatedBy NUMBER(11,0) DEFAULT 0 NOT NULL, - Creator NUMBER(11,0) DEFAULT 0 NOT NULL, - Created DATE -); - - -CREATE SEQUENCE TICKETCUSTOMFIELDVALUES_seq; -CREATE TABLE TicketCustomFieldValues ( - id NUMBER(11,0) - CONSTRAINT TicketCustomFieldValues_Key PRIMARY KEY, - Ticket NUMBER(11,0), - CustomField NUMBER(11,0) NOT NULL, - Content VARCHAR2(255), - Creator NUMBER(11,0) DEFAULT 0 NOT NULL, - Created DATE, - LastUpdatedBy NUMBER(11,0) DEFAULT 0 NOT NULL, - LastUpdated DATE -); - -CREATE INDEX TicketCustomFieldValues1 ON TicketCustomFieldValues (CustomField,Ticket,Content); -CREATE INDEX TicketCustomFieldValues2 ON TicketCustomFieldValues (CustomField,Ticket); - -CREATE SEQUENCE CUSTOMFIELDS_seq; -CREATE TABLE CustomFields ( - id NUMBER(11,0) - CONSTRAINT CustomFields_Key PRIMARY KEY, - Name VARCHAR2(200), - Type VARCHAR2(200), - Queue NUMBER(11,0) DEFAULT 0 NOT NULL, - Description VARCHAR2(255), - SortOrder NUMBER(11,0) DEFAULT 0 NOT NULL, - Creator NUMBER(11,0) DEFAULT 0 NOT NULL, - Created DATE, - LastUpdatedBy NUMBER(11,0) DEFAULT 0 NOT NULL, - LastUpdated DATE, - Disabled NUMBER(11,0) DEFAULT 0 NOT NULL -); -CREATE INDEX CustomFields1 ON CustomFields (Disabled, Queue); - - -CREATE SEQUENCE CUSTOMFIELDVALUES_seq; -CREATE TABLE CustomFieldValues ( - id NUMBER(11,0) - CONSTRAINT CustomFieldValues_Key PRIMARY KEY, - CustomField NUMBER(11,0), - Name VARCHAR2(200), - Description VARCHAR2(255), - SortOrder NUMBER(11,0) DEFAULT 0 NOT NULL, - Creator NUMBER(11,0) DEFAULT 0 NOT NULL, - Created DATE, - LastUpdatedBy NUMBER(11,0) DEFAULT 0 NOT NULL, - LastUpdated DATE -); - -CREATE INDEX CustomFieldValues1 ON CustomFieldValues (CustomField); - -CREATE TABLE sessions ( - id VARCHAR2(32) - CONSTRAINT Sessions_Key PRIMARY KEY, - a_session CLOB, - LastUpdated DATE -); - diff --git a/rt/etc/schema.Pg b/rt/etc/schema.Pg index 085c61595..ba0d6fc6c 100755 --- a/rt/etc/schema.Pg +++ b/rt/etc/schema.Pg @@ -3,6 +3,9 @@ -- ------------------------------------------------------------------ +BEGIN; + + -- @@ -90,7 +93,6 @@ CREATE TABLE Links ( ); CREATE UNIQUE INDEX Links1 ON Links (Base, Target, Type) ; -CREATE INDEX Links4 ON Links(Type,LocalBase); -- }}} @@ -134,7 +136,7 @@ CREATE TABLE Groups ( Description varchar(255) NULL , Domain varchar(64), Type varchar(64), - Instance integer, + Instance varchar(64), PRIMARY KEY (id) ); @@ -190,7 +192,7 @@ CREATE TABLE Transactions ( Field varchar(40) NULL , OldValue varchar(255) NULL , NewValue varchar(255) NULL , - Data varchar(255) NULL , + Data varchar(100) NULL , Creator integer NOT NULL DEFAULT 0 , Created TIMESTAMP NULL , @@ -498,9 +500,6 @@ CREATE TABLE TicketCustomFieldValues ( ); -CREATE INDEX TicketCustomFieldValues1 ON TicketCustomFieldValues (CustomField,Ticket,Content); -CREATE INDEX TicketCustomFieldValues2 ON TicketCustomFieldValues (CustomField,Ticket); - -- }}} -- {{{ CustomFields @@ -557,8 +556,6 @@ CREATE TABLE CustomFieldValues ( ); -CREATE INDEX CustomFieldValues1 ON CustomFieldValues (CustomField); - -- }}} -- {{{ Sessions @@ -576,3 +573,6 @@ CREATE TABLE sessions ( -- }}} + + +COMMIT; diff --git a/rt/etc/schema.mysql b/rt/etc/schema.mysql index 14e92238f..46f8ec562 100755 --- a/rt/etc/schema.mysql +++ b/rt/etc/schema.mysql @@ -62,7 +62,6 @@ CREATE TABLE Links ( CREATE UNIQUE INDEX Links1 ON Links (Base, Target, Type) ; CREATE INDEX Links2 ON Links (Base, Type) ; CREATE INDEX Links3 ON Links (Target, Type) ; -CREATE INDEX Links4 ON Links(Type,LocalBase); # }}} @@ -88,7 +87,7 @@ CREATE TABLE Groups ( Description varchar(255) NULL , Domain varchar(64), Type varchar(64), - Instance integer, + Instance varchar(64), PRIMARY KEY (id) ) TYPE=InnoDB; @@ -126,7 +125,7 @@ CREATE TABLE Transactions ( Field varchar(40) NULL , OldValue varchar(255) NULL , NewValue varchar(255) NULL , - Data varchar(255) NULL , + Data varchar(100) NULL , Creator integer NOT NULL DEFAULT 0 , Created DATETIME NULL , @@ -359,9 +358,6 @@ CREATE TABLE TicketCustomFieldValues ( PRIMARY KEY (id) ) TYPE=InnoDB; -CREATE INDEX TicketCustomFieldValues1 ON TicketCustomFieldValues (CustomField,Ticket,Content); -CREATE INDEX TicketCustomFieldValues2 ON TicketCustomFieldValues (CustomField,Ticket); - # }}} # {{{ CustomFields @@ -403,8 +399,6 @@ CREATE TABLE CustomFieldValues ( PRIMARY KEY (id) ) TYPE=InnoDB; -CREATE INDEX CustomFieldValues1 ON CustomFieldValues (CustomField); - # }}} # {{{ Sessions diff --git a/rt/lib/RT.pm b/rt/lib/RT.pm index 7e941a2b2..90c332bc0 100644 --- a/rt/lib/RT.pm +++ b/rt/lib/RT.pm @@ -47,7 +47,7 @@ use vars qw($VERSION $System $SystemUser $Nobody $Handle $Logger $MasonSessionDir ); -$VERSION = '3.0.9'; +$VERSION = '3.0.4'; $CORE_CONFIG_FILE = "/opt/rt3/etc/RT_Config.pm"; $SITE_CONFIG_FILE = "/opt/rt3/etc/RT_SiteConfig.pm"; @@ -117,10 +117,13 @@ sub LoadConfig { =cut sub Init { - + require RT::Handle; #Get a database connection - ConnectToDatabase(); - + unless ($Handle && $Handle->dbh->ping) { + $Handle = RT::Handle->new(); + } + $Handle->Connect(); + #RT's system user is a genuine database user. its id lives here $SystemUser = new RT::CurrentUser(); $SystemUser->LoadByName('RT_System'); @@ -134,21 +137,6 @@ sub Init { InitLogging(); } - -=head2 ConnectToDatabase - -Get a database connection - -=cut - -sub ConnectToDatabase { - require RT::Handle; - unless ($Handle && $Handle->dbh && $Handle->dbh->ping) { - $Handle = RT::Handle->new(); - } - $Handle->Connect(); -} - =head2 InitLogging Create the RT::Logger object. @@ -294,15 +282,8 @@ sub DropSetGIDPermissions { =head1 BUGS -Please report them to rt-3.0-bugs@fsck.com, if you know what's broken and have at least some idea of what needs to be fixed. -If you're not sure what's going on, report them rt-devel@lists.fsck.com. - =head1 SEE ALSO -L<RT::StyleGuide> -L<DBIx::SearchBuilder> - - =begin testing diff --git a/rt/lib/RT/Action/Autoreply.pm b/rt/lib/RT/Action/Autoreply.pm index f58b8f284..81f7bddfa 100755 --- a/rt/lib/RT/Action/Autoreply.pm +++ b/rt/lib/RT/Action/Autoreply.pm @@ -74,18 +74,10 @@ sub SetReturnAddress { } unless ($self->TemplateObj->MIMEObj->head->get('From')) { - if ($RT::UseFriendlyFromLine) { - my $friendly_name = $self->TicketObj->QueueObj->Description || - $self->TicketObj->QueueObj->Name; - $friendly_name =~ s/"/\\"/g; - $self->SetHeader( 'From', - sprintf($RT::FriendlyFromLineFormat, - $self->MIMEEncodeString( $friendly_name, $RT::EmailOutputEncoding ), $replyto), - ); - } - else { - $self->SetHeader( 'From', $replyto ); - } + my $friendly_name = $self->TicketObj->QueueObj->Description || + $self->TicketObj->QueueObj->Name; + $friendly_name =~ s/"/\\"/g; + $self->SetHeader('From', "\"$friendly_name\" <$replyto>"); } unless ($self->TemplateObj->MIMEObj->head->get('Reply-To')) { diff --git a/rt/lib/RT/Action/SendEmail.pm b/rt/lib/RT/Action/SendEmail.pm index 645c5d99d..dac8fc8e7 100755 --- a/rt/lib/RT/Action/SendEmail.pm +++ b/rt/lib/RT/Action/SendEmail.pm @@ -129,7 +129,7 @@ sub Commit { $self->SetHeader( 'Cc', join ( ',', @{ $self->{'Cc'} } ) ) if ( $self->{'Cc'} && @{ $self->{'Cc'} } ); $self->SetHeader( 'Bcc', join ( ',', @{ $self->{'Bcc'} } ) ) - if ( $self->{'Bcc'} && @{ $self->{'Bcc'} } ); + if ( $self->{'Cc'} && @{ $self->{'Bcc'} } ); $self->SetHeader('MIME-Version', '1.0'); @@ -239,7 +239,7 @@ sub Prepare { =head2 SendMessage MIMEObj sends the message using RT's preferred API. -TODO: Break this out to a separate module +TODO: Break this out to a seperate module =cut @@ -266,7 +266,7 @@ sub SendMessage { and ( !$MIMEObj->head->get('To') ) ); if ( $RT::MailCommand eq 'sendmailpipe' ) { eval { - open( MAIL, "|$RT::SendmailPath $RT::SendmailArguments" ) || die $!; + open( MAIL, "|$RT::SendmailPath $RT::SendmailArguments" ); print MAIL $MIMEObj->as_string; close(MAIL); }; @@ -279,7 +279,7 @@ sub SendMessage { local $ENV{MAILADDRESS}; if ( $RT::MailCommand eq 'sendmail' ) { - push @mailer_args, split(/\s+/, $RT::SendmailArguments); + push @mailer_args, $RT::SendmailArguments; } elsif ( $RT::MailCommand eq 'smtp' ) { $ENV{MAILADDRESS} = $RT::SMTPFrom || $MIMEObj->head->get('From'); @@ -663,41 +663,15 @@ Takes a string and a possible encoding and returns the string wrapped in MIME go sub MIMEEncodeString { my $self = shift; my $value = shift; - # using RFC2047 notation, sec 2. - # encoded-word = "=?" charset "?" encoding "?" encoded-text "?=" - my $charset = shift; - my $encoding = 'B'; - # An 'encoded-word' may not be more than 75 characters long - # - # MIME encoding increases 4/3*(number of bytes), and always in multiples - # of 4. Thus we have to find the best available value of bytes available - # for each chunk. - # - # First we get the integer max which max*4/3 would fit on space. - # Then we find the greater multiple of 3 lower or equal than $max. - my $max = int(((75-length('=?'.$charset.'?'.$encoding.'?'.'?='))*3)/4); - $max = int($max/3)*3; + my $enc = shift; chomp $value; return ($value) unless $value =~ /[^\x20-\x7e]/; $value =~ s/\s*$//; Encode::_utf8_off($value); - my $res = Encode::from_to( $value, "utf-8", $charset ); - - if ($max > 0) { - # copy value and split in chuncks - my $str=$value; - my @chunks = unpack("a$max" x int(length($str)/$max - + ((length($str) % $max) ? 1:0)), $str); - # encode an join chuncks - $value = join " ", - map encode_mimeword( $_, $encoding, $charset ), @chunks ; - return($value); - } else { - # gives an error... - $RT::Logger->crit("Can't encode! Charset or encoding too big.\n"); - } + my $res = Encode::from_to( $value, "utf-8", $enc ); + $value = encode_mimeword( $value, 'B', $enc ); } # }}} diff --git a/rt/lib/RT/CurrentUser.pm b/rt/lib/RT/CurrentUser.pm index abaf87608..4ca2f9891 100755 --- a/rt/lib/RT/CurrentUser.pm +++ b/rt/lib/RT/CurrentUser.pm @@ -70,7 +70,7 @@ sub _Init { $self->Load($Name); } - # $self->CurrentUser($self); + $self->CurrentUser($self); } # }}} @@ -104,13 +104,15 @@ sub Delete { sub UserObj { my $self = shift; + unless ($self->{'UserObj'}) { use RT::User; - my $user = RT::User->new($self); - - unless ($user->Load($self->Id)) { + $self->{'UserObj'} = RT::User->new($self); + unless ($self->{'UserObj'}->Load($self->Id)) { $RT::Logger->err($self->loc("Couldn't load [_1] from the users database.\n", $self->Id)); } - return ($user); + + } + return ($self->{'UserObj'}); } # }}} @@ -158,7 +160,6 @@ sub _Accessible { Gecos => 'read', RealName => 'read', Password => 'neither', - Lang => 'read', EmailAddress => 'read', Privileged => 'read', IsAdministrator => 'read' @@ -240,11 +241,6 @@ sub Load { if ($identifier !~ /\D/) { $self->SUPER::LoadById($identifier); } - - elsif (UNIVERSAL::isa($identifier,"RT::User")) { - # DWIM if they pass a user in - $self->SUPER::LoadById($identifier->Id); - } else { # This is a bit dangerous, we might get false authen if somebody # uses ambigous userids or real names: @@ -330,24 +326,16 @@ ok ($cu->loc('Before') eq "Avant", "Localized TEST_STRING into Frenc"); sub LanguageHandle { my $self = shift; - if ( ( !defined $self->{'LangHandle'} ) - || ( !UNIVERSAL::can( $self->{'LangHandle'}, 'maketext' ) ) - || (@_) ) { - if ( (!$RT::SystemUser || $self->id == $RT::SystemUser->id() )) { - @_ = qw(en-US); - } - - elsif ( $self->Lang ) { - push @_, $self->Lang; - } + if ((!defined $self->{'LangHandle'}) || + (!UNIVERSAL::can($self->{'LangHandle'}, 'maketext')) || + (@_)) { $self->{'LangHandle'} = RT::I18N->get_handle(@_); } - # Fall back to english. - unless ( $self->{'LangHandle'} ) { + unless ($self->{'LangHandle'}) { die "We couldn't get a dictionary. Nye mogu naidti slovar. No puedo encontrar dictionario."; } - return ( $self->{'LangHandle'} ); + return ($self->{'LangHandle'}); } sub loc { @@ -377,19 +365,6 @@ sub loc_fuzzy { } # }}} - -=head2 CurrentUser - -Return the current currentuser object - -=cut - -sub CurrentUser { - my $self = shift; - return($self); - -} - eval "require RT::CurrentUser_Vendor"; die $@ if ($@ && $@ !~ qr{^Can't locate RT/CurrentUser_Vendor.pm}); eval "require RT::CurrentUser_Local"; diff --git a/rt/lib/RT/Date.pm b/rt/lib/RT/Date.pm index 609db1b45..355370ada 100644 --- a/rt/lib/RT/Date.pm +++ b/rt/lib/RT/Date.pm @@ -529,24 +529,6 @@ sub ISO { # }}} -# {{{ sub W3CDTF - -=head2 W3CDTF - -Takes nothing - -Returns the object's date in W3C DTF format - -=cut - -sub W3CDTF { - my $self = shift; - my $date = $self->ISO . 'Z'; - $date =~ s/ /T/; - return $date; -}; - -# }}} # {{{ sub LocalTimezone =head2 LocalTimezone diff --git a/rt/lib/RT/Handle.pm b/rt/lib/RT/Handle.pm index e60bbd924..5cdb65e5b 100644 --- a/rt/lib/RT/Handle.pm +++ b/rt/lib/RT/Handle.pm @@ -58,17 +58,15 @@ Takes nothing. Calls SUPER::Connect with the needed args =cut sub Connect { - my $self = shift; - - if ( $RT::DatabaseType eq 'Oracle' ) { - $ENV{'NLS_LANG'} = ".UTF8"; - } +my $self=shift; - $self->SUPER::Connect( User => $RT::DatabaseUser, - Password => $RT::DatabasePassword, ); - - $self->dbh->{LongReadLen} = $RT::MaxAttachmentSize; +# Unless the database port is a positive integer, we really don't want to pass it. +$self->SUPER::Connect( + User => $RT::DatabaseUser, + Password => $RT::DatabasePassword, + ); + } =item BuildDSN @@ -81,11 +79,9 @@ from the config file. sub BuildDSN { my $self = shift; -# Unless the database port is a positive integer, we really don't want to pass it. $RT::DatabasePort = undef unless (defined $RT::DatabasePort && $RT::DatabasePort =~ /^(\d+)$/); $RT::DatabaseHost = undef unless (defined $RT::DatabaseHost && $RT::DatabaseHost ne ''); - $self->SUPER::BuildDSN(Host => $RT::DatabaseHost, Database => $RT::DatabaseName, Port => $RT::DatabasePort, diff --git a/rt/lib/RT/Interface/CLI.pm b/rt/lib/RT/Interface/CLI.pm index a3c840af5..ec0e877b4 100644 --- a/rt/lib/RT/Interface/CLI.pm +++ b/rt/lib/RT/Interface/CLI.pm @@ -33,7 +33,7 @@ BEGIN { use vars qw ($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS); # set the version for version checking - $VERSION = do { my @r = (q$Revision: 1.1.1.1 $ =~ /\d+/g); sprintf "%d."."%02d" x $#r, @r }; # must be all one line, for MakeMaker + $VERSION = do { my @r = (q$Revision: 1.2 $ =~ /\d+/g); sprintf "%d."."%02d" x $#r, @r }; # must be all one line, for MakeMaker @ISA = qw(Exporter); diff --git a/rt/lib/RT/Interface/Email.pm b/rt/lib/RT/Interface/Email.pm index 52542a149..7eec0502f 100755 --- a/rt/lib/RT/Interface/Email.pm +++ b/rt/lib/RT/Interface/Email.pm @@ -27,14 +27,14 @@ use strict; use Mail::Address; use MIME::Entity; use RT::EmailParser; -use File::Temp; + BEGIN { use Exporter (); use vars qw ($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS); # set the version for version checking - $VERSION = do { my @r = (q$Revision: 1.1.1.3 $ =~ /\d+/g); sprintf "%d."."%02d" x $#r, @r }; # must be all one line, for MakeMaker + $VERSION = do { my @r = (q$Revision: 1.2 $ =~ /\d+/g); sprintf "%d."."%02d" x $#r, @r }; # must be all one line, for MakeMaker @ISA = qw(Exporter); @@ -57,7 +57,7 @@ BEGIN { =head1 NAME - RT::Interface::Email - helper functions for parsing email sent to RT + RT::Interface::CLI - helper functions for creating a commandline RT interface =head1 SYNOPSIS @@ -153,7 +153,6 @@ sub MailError { Subject => 'There has been an error', Explanation => 'Unexplained error', MIMEObj => undef, - Attach => undef, LogLevel => 'crit', @_); @@ -176,19 +175,14 @@ sub MailError { $mimeobj->sync_headers(); $entity->add_part($mimeobj); } - - if ($args{'Attach'}) { - $entity->attach(Data => $args{'Attach'}, Type => 'message/rfc822'); - - } - + if ($RT::MailCommand eq 'sendmailpipe') { open (MAIL, "|$RT::SendmailPath $RT::SendmailArguments") || return(0); print MAIL $entity->as_string; close(MAIL); } else { - $entity->send($RT::MailCommand, $RT::MailParams); + $entity->send($RT::MailCommand, $RT::MailParams); } } @@ -200,6 +194,12 @@ sub CreateUser { my ($Username, $Address, $Name, $ErrorsTo, $entity) = @_; my $NewUser = RT::User->new($RT::SystemUser); + # This data is tainted by some Very Broken mailers. + # (Sometimes they send raw ISO 8859-1 data here. fear that. + require Encode; + $Username = Encode::encode(utf8 => $Username, Encode::FB_PERLQQ()) if defined $Username; + $Name = Encode::encode(utf8 => $Name, Encode::FB_PERLQQ()) if defined $Name; + my ($Val, $Message) = $NewUser->Create(Name => ($Username || $Address), EmailAddress => $Address, @@ -361,111 +361,36 @@ sub ParseAddressFromHeader{ -=head2 Gateway ARGSREF - - -Takes parameters: - - action - queue - message - +=head2 Gateway This performs all the "guts" of the mail rt-mailgate program, and is designed to be called from the web interface with a message, user object, and so on. -Can also take an optional 'ticket' parameter; this ticket id overrides -any ticket id found in the subject. - -Returns: - - An array of: - - (status code, message, optional ticket object) - - status code is a numeric value. - - for temporary failures, status code should be -75 - - for permanent failures which are handled by RT, status code should be 0 - - for succces, the status code should be 1 - - - =cut sub Gateway { - my $argsref = shift; - - my %args = %$argsref; - - # Set some reasonable defaults - $args{'action'} = 'correspond' unless ( $args{'action'} ); - $args{'queue'} = '1' unless ( $args{'queue'} ); + my %args = ( message => undef, + queue => 1, + action => 'correspond', + ticket => undef, + @_ ); # Validate the action unless ( $args{'action'} =~ /^(comment|correspond|action)$/ ) { # Can't safely loc this. What object do we loc around? - $RT::Logger->crit("Mail gateway called with an invalid action paramenter '".$args{'action'}."' for queue '".$args{'queue'}."'"); - - return ( -75, "Invalid 'action' parameter", undef ); + return ( 0, "Invalid 'action' parameter", undef ); } my $parser = RT::EmailParser->new(); - my ( $fh, $temp_file ); - for ( 1 .. 10 ) { - - # on NFS and NTFS, it is possible that tempfile() conflicts - # with other processes, causing a race condition. we try to - # accommodate this by pausing and retrying. - last if ( $fh, $temp_file ) = eval { File::Temp::tempfile(undef, UNLINK => 0) }; - sleep 1; - } - if ($fh) { - binmode $fh; #thank you, windows - $fh->autoflush(1); - print $fh $args{'message'}; - close($fh); - - if ( -f $temp_file ) { - $parser->ParseMIMEEntityFromFile($temp_file); - unlink( $temp_file ); - if ($parser->Entity) { - delete $args{'message'}; - } - } - - } - - #If for some reason we weren't able to parse the message using a temp file - # try it with a scalar - if ($args{'message'}) { - $parser->ParseMIMEEntityFromScalar($args{'message'}); - - } - - if (!$parser->Entity()) { - MailError( - To => $RT::OwnerEmail, - Subject => "RT Bounce: Unparseable message", - Explanation => "RT couldn't process the message below", - Attach => $args{'message'} - ); - - return(0,"Failed to parse this message. Something is likely badly wrong with the message"); - } + $parser->ParseMIMEEntityFromScalar( $args{'message'} ); my $Message = $parser->Entity(); - my $head = $Message->head; + my $head = $Message->head; my ( $CurrentUser, $AuthStat, $status, $error ); - # Initalize AuthStat so comparisons work correctly - $AuthStat = -9999999; - my $ErrorsTo = ParseErrorsToAddressFromHead($head); my $MessageId = $head->get('Message-Id') @@ -475,31 +400,37 @@ sub Gateway { my $Subject = $head->get('Subject') || ''; chomp $Subject; + $args{'ticket'} ||= $parser->ParseTicketId($Subject); my $SystemTicket; - if ( $args{'ticket'} ) { + if ($args{'ticket'} ) { $SystemTicket = RT::Ticket->new($RT::SystemUser); - $SystemTicket->Load( $args{'ticket'} ); + $SystemTicket->Load($args{'ticket'}); } #Set up a queue object my $SystemQueueObj = RT::Queue->new($RT::SystemUser); $SystemQueueObj->Load( $args{'queue'} ); + # We can safely have no queue of we have a known-good ticket unless ( $args{'ticket'} || $SystemQueueObj->id ) { - return ( -75, "RT couldn't find the queue: " . $args{'queue'}, undef ); + MailError( + To => $RT::OwnerEmail, + Subject => "RT Bounce: $Subject", + Explanation => "RT couldn't find the queue: " . $args{'queue'}, + MIMEObj => $Message ); + return ( 0, "RT couldn't find the queue: " . $args{'queue'}, undef ); } # Authentication Level - # -1 - Get out. this user has been explicitly declined + # -1 - Get out. this user has been explicitly declined # 0 - User may not do anything (Not used at the moment) # 1 - Normal user # 2 - User is allowed to specify status updates etc. a la enhanced-mailgate - push @RT::MailPlugins, "Auth::MailFrom" unless @RT::MailPlugins; - + push @RT::MailPlugins, "Auth::MailFrom" unless @RT::MailPlugins; # Since this needs loading, no matter what for (@RT::MailPlugins) { @@ -522,59 +453,35 @@ sub Gateway { } } - ( $CurrentUser, $NewAuthStat ) = $Code->( - Message => $Message, - CurrentUser => $CurrentUser, - AuthLevel => $AuthStat, - Action => $args{'action'}, - Ticket => $SystemTicket, - Queue => $SystemQueueObj - ); - - # If a module returns a "-1" then we discard the ticket, so. - $AuthStat = -1 if $NewAuthStat == -1; + ( $CurrentUser, $NewAuthStat ) = $Code->( Message => $Message, + CurrentUser => $CurrentUser, + AuthLevel => $AuthStat, + Action => $args{'action'}, + Ticket => $SystemTicket, + Queue => $SystemQueueObj ); # You get the highest level of authentication you were assigned. - $AuthStat = $NewAuthStat if $NewAuthStat > $AuthStat; last if $AuthStat == -1; + $AuthStat = $NewAuthStat if $NewAuthStat > $AuthStat; } # {{{ If authentication fails and no new user was created, get out. if ( !$CurrentUser or !$CurrentUser->Id or $AuthStat == -1 ) { # If the plugins refused to create one, they lose. - unless ( $AuthStat == -1 ) { - - # Notify the RT Admin of the failure. - # XXX Should this be configurable? - MailError( - To => $RT::OwnerEmail, - Subject => "Could not load a valid user", - Explanation => <<EOT, -RT could not load a valid user, and RT's configuration does not allow -for the creation of a new user for this email ($ErrorsTo). - -You might need to grant 'Everyone' the right 'CreateTicket' for the -queue @{[$args{'queue'}]}. - -EOT - MIMEObj => $Message, - LogLevel => 'error' - ); - - # Also notify the requestor that his request has been dropped. - MailError( - To => $ErrorsTo, - Subject => "Could not load a valid user", - Explanation => <<EOT, + MailError( + Subject => "Could not load a valid user", + Explanation => <<EOT, RT could not load a valid user, and RT's configuration does not allow for the creation of a new user for your email. +Your RT administrator needs to grant 'Everyone' the right 'CreateTicket' +for this queue. + EOT - MIMEObj => $Message, - LogLevel => 'error' - ); - } + MIMEObj => $Message, + LogLevel => 'error' ) + unless $AuthStat == -1; return ( 0, "Could not load a valid user", undef ); } @@ -601,11 +508,10 @@ EOT # {{{ Drop it if it's disallowed if ( $AuthStat == 0 ) { MailError( - To => $ErrorsTo, - Subject => "Permission Denied", - Explanation => "You do not have permission to communicate with RT", - MIMEObj => $Message - ); + To => $ErrorsTo, + Subject => "Permission Denied", + Explanation => "You do not have permission to communicate with RT", + MIMEObj => $Message ); } # }}} @@ -617,12 +523,10 @@ EOT #Should we mail it to RTOwner? if ($RT::LoopsToRTOwner) { - MailError( - To => $RT::OwnerEmail, - Subject => "RT Bounce: $Subject", - Explanation => "RT thinks this message may be a bounce", - MIMEObj => $Message - ); + MailError( To => $RT::OwnerEmail, + Subject => "RT Bounce: $Subject", + Explanation => "RT thinks this message may be a bounce", + MIMEObj => $Message ); #Do we actually want to store it? return ( 0, "Message Bounced", undef ) unless ($RT::StoreLoops); @@ -634,10 +538,8 @@ EOT # {{{ Squelch replies if necessary # Don't let the user stuff the RT-Squelch-Replies-To header. if ( $head->get('RT-Squelch-Replies-To') ) { - $head->add( - 'RT-Relocated-Squelch-Replies-To', - $head->get('RT-Squelch-Replies-To') - ); + $head->add( 'RT-Relocated-Squelch-Replies-To', + $head->get('RT-Squelch-Replies-To') ); $head->delete('RT-Squelch-Replies-To'); } @@ -662,27 +564,22 @@ EOT my @Requestors = ( $CurrentUser->id ); if ($RT::ParseNewMessageForTicketCcs) { - @Cc = ParseCcAddressesFromHead( - Head => $head, - CurrentUser => $CurrentUser, - QueueObj => $SystemQueueObj - ); + @Cc = ParseCcAddressesFromHead( Head => $head, + CurrentUser => $CurrentUser, + QueueObj => $SystemQueueObj ); } my ( $id, $Transaction, $ErrStr ) = $Ticket->Create( - Queue => $SystemQueueObj->Id, - Subject => $Subject, - Requestor => \@Requestors, - Cc => \@Cc, - MIMEObj => $Message - ); + Queue => $SystemQueueObj->Id, + Subject => $Subject, + Requestor => \@Requestors, + Cc => \@Cc, + MIMEObj => $Message ); if ( $id == 0 ) { - MailError( - To => $ErrorsTo, - Subject => "Ticket creation failed", - Explanation => $ErrStr, - MIMEObj => $Message - ); + MailError( To => $ErrorsTo, + Subject => "Ticket creation failed", + Explanation => $ErrStr, + MIMEObj => $Message ); $RT::Logger->error("Create failed: $id / $Transaction / $ErrStr "); return ( 0, "Ticket creation failed", $Ticket ); } @@ -694,17 +591,15 @@ EOT # If the action is comment, add a comment. elsif ( $args{'action'} =~ /^(comment|correspond)$/i ) { - $Ticket->Load( $args{'ticket'} ); + $Ticket->Load($args{'ticket'}); unless ( $Ticket->Id ) { - my $message = "Could not find a ticket with id " . $args{'ticket'}; - MailError( - To => $ErrorsTo, - Subject => "Message not recorded", - Explanation => $message, - MIMEObj => $Message - ); - - return ( 0, $message ); + my $message = "Could not find a ticket with id ".$args{'ticket'}; + MailError( To => $ErrorsTo, + Subject => "Message not recorded", + Explanation => $message, + MIMEObj => $Message ); + + return ( 0, $message); } my ( $status, $msg ); @@ -717,12 +612,10 @@ EOT unless ($status) { #Warn the sender that we couldn't actually submit the comment. - MailError( - To => $ErrorsTo, - Subject => "Message not recorded", - Explanation => $msg, - MIMEObj => $Message - ); + MailError( To => $ErrorsTo, + Subject => "Message not recorded", + Explanation => $msg, + MIMEObj => $Message ); return ( 0, "Message not recorded", $Ticket ); } } @@ -730,28 +623,21 @@ EOT else { #Return mail to the sender with an error - MailError( - To => $ErrorsTo, - Subject => "RT Configuration error", - Explanation => "'" - . $args{'action'} - . "' not a recognized action." - . " Your RT administrator has misconfigured " - . "the mail aliases which invoke RT", - MIMEObj => $Message - ); + MailError( To => $ErrorsTo, + Subject => "RT Configuration error", + Explanation => "'" + . $args{'action'} + . "' not a recognized action." + . " Your RT administrator has misconfigured " + . "the mail aliases which invoke RT", + MIMEObj => $Message ); $RT::Logger->crit( $args{'action'} . " type unknown for $MessageId" ); - return ( - -75, - "Configuration error: " - . $args{'action'} - . " not a recognized action", - $Ticket - ); + return ( 0, "Configuration error: " . $args{'action'} . " not a recognized action", $Ticket ); } - return ( 1, "Success", $Ticket ); + +return ( 1, "Success", $Ticket ); } eval "require RT::Interface::Email_Vendor"; diff --git a/rt/lib/RT/Interface/Web.pm b/rt/lib/RT/Interface/Web.pm index 329a2801b..5097f54a4 100644 --- a/rt/lib/RT/Interface/Web.pm +++ b/rt/lib/RT/Interface/Web.pm @@ -68,7 +68,6 @@ sub NewApacheHandler { default_escape_flags => 'h', allow_globals => [qw(%session)], data_dir => "$RT::MasonDataDir", - autoflush => 1, @_ ); @@ -99,9 +98,7 @@ sub NewCGIHandler { ], data_dir => "$RT::MasonDataDir", default_escape_flags => 'h', - allow_globals => [qw(%session)], - autoflush => 1, - @_ + allow_globals => [qw(%session)] ); @@ -140,60 +137,6 @@ sub EscapeUTF8 { # }}} -# {{{ WebCanonicalizeInfo - -=head2 WebCanonicalizeInfo(); - -Different web servers set different environmental varibles. This -function must return something suitable for REMOTE_USER. By default, -just downcase $ENV{'REMOTE_USER'} - -=cut - -sub WebCanonicalizeInfo { - my $user; - - if ( defined $ENV{'REMOTE_USER'} ) { - $user = lc ( $ENV{'REMOTE_USER'} ) if( length($ENV{'REMOTE_USER'}) ); - } - - return $user; -} - -# }}} - -# {{{ WebExternalAutoInfo - -=head2 WebExternalAutoInfo($user); - -Returns a hash of user attributes, used when WebExternalAuto is set. - -=cut - -sub WebExternalAutoInfo { - my $user = shift; - - my %user_info; - - $user_info{'Privileged'} = 1; - - if ($^O !~ /^(?:riscos|MacOS|MSWin32|dos|os2)$/) { - # Populate fields with information from Unix /etc/passwd - - my ($comments, $realname) = (getpwnam($user))[5, 6]; - $user_info{'Comments'} = $comments if defined $comments; - $user_info{'RealName'} = $realname if defined $realname; - } - elsif ($^O eq 'MSWin32' and eval 'use Net::AdminMisc; 1') { - # Populate fields with information from NT domain controller - } - - # and return the wad of stuff - return {%user_info}; -} - -# }}} - package HTML::Mason::Commands; use strict; @@ -217,13 +160,10 @@ sub loc { UNIVERSAL::can($session{'CurrentUser'}, 'loc')){ return($session{'CurrentUser'}->loc(@_)); } - elsif ( my $u = eval { RT::CurrentUser->new($RT::SystemUser->Id) } ) { + else { + my $u = RT::CurrentUser->new($RT::SystemUser); return ($u->loc(@_)); } - else { - # pathetic case -- SystemUser is gone. - return $_[0]; - } } # }}} @@ -249,7 +189,7 @@ sub loc_fuzzy { return($session{'CurrentUser'}->loc_fuzzy($msg)); } else { - my $u = RT::CurrentUser->new($RT::SystemUser->Id); + my $u = RT::CurrentUser->new($RT::SystemUser); return ($u->loc_fuzzy($msg)); } } @@ -425,9 +365,7 @@ sub ProcessUpdateMessage { ); #Make the update content have no 'weird' newlines in it - if ( $args{ARGSRef}->{'UpdateTimeWorked'} || - $args{ARGSRef}->{'UpdateContent'} || - $args{ARGSRef}->{'UpdateAttachments'}) { + if ( $args{ARGSRef}->{'UpdateContent'} ) { if ( $args{ARGSRef}->{'UpdateSubject'} eq $args{'TicketObj'}->Subject() ) @@ -495,8 +433,7 @@ sub MakeMIMEEntity { Cc => undef, Body => undef, AttachmentFieldName => undef, -# map Encode::encode_utf8($_), @_, - @_, + map Encode::encode_utf8($_), @_, ); #Make the update content have no 'weird' newlines in it @@ -512,7 +449,6 @@ sub MakeMIMEEntity { Subject => $args{'Subject'} || "", From => $args{'From'}, Cc => $args{'Cc'}, - Charset => 'utf8', Data => [ $args{'Body'} ] ); } @@ -527,14 +463,7 @@ sub MakeMIMEEntity { #foreach my $filehandle (@filenames) { - my ( $fh, $temp_file ); - for ( 1 .. 10 ) { - # on NFS and NTFS, it is possible that tempfile() conflicts - # with other processes, causing a race condition. we try to - # accommodate this by pausing and retrying. - last if ($fh, $temp_file) = eval { tempfile( UNLINK => 1) }; - sleep 1; - } + my ( $fh, $temp_file ) = tempfile(); binmode $fh; #thank you, windows my ($buffer); @@ -552,7 +481,7 @@ sub MakeMIMEEntity { $Message->attach( Path => $temp_file, - Filename => Encode::decode_utf8($filename), + Filename => $filename, Type => $uploadinfo->{'Content-Type'}, ); close($fh); @@ -665,13 +594,13 @@ sub ProcessSearchQuery { # }}} # {{{ Limit requestor email - if ( $args{ARGS}->{'ValueOfWatcherRole'} ne '' ) { - $session{'tickets'}->LimitWatcher( - TYPE => $args{ARGS}->{'WatcherRole'}, - VALUE => $args{ARGS}->{'ValueOfWatcherRole'}, - OPERATOR => $args{ARGS}->{'WatcherRoleOp'}, + if ( $args{ARGS}->{'ValueOfRequestor'} ne '' ) { + my $alias = $session{'tickets'}->LimitRequestor( + VALUE => $args{ARGS}->{'ValueOfRequestor'}, + OPERATOR => $args{ARGS}->{'RequestorOp'}, ); + } # }}} @@ -851,13 +780,17 @@ sub ProcessACLChanges { my $obj; - if ($object_type eq 'RT::System') { - $obj = $RT::System; - } elsif ($RT::ACE::OBJECT_TYPES{$object_type}) { - $obj = $object_type->new($session{'CurrentUser'}); + if ($object_type eq 'RT::Queue') { + $obj = RT::Queue->new($session{'CurrentUser'}); $obj->Load($object_id); + } elsif ($object_type eq 'RT::Group') { + $obj = RT::Group->new($session{'CurrentUser'}); + $obj->Load($object_id); + + } elsif ($object_type eq 'RT::System') { + $obj = $RT::System; } else { - push (@results, loc("System Error"). ': '. + push (@results, loc("System Error"). loc("Rights could not be granted for [_1]", $object_type)); next; } @@ -880,14 +813,17 @@ sub ProcessACLChanges { next unless ($right); my $obj; - if ($object_type eq 'RT::System') { - $obj = $RT::System; - } elsif ($RT::ACE::OBJECT_TYPES{$object_type}) { - $obj = $object_type->new($session{'CurrentUser'}); + if ($object_type eq 'RT::Queue') { + $obj = RT::Queue->new($session{'CurrentUser'}); + $obj->Load($object_id); + } elsif ($object_type eq 'RT::Group') { + $obj = RT::Group->new($session{'CurrentUser'}); $obj->Load($object_id); + + } elsif ($object_type eq 'RT::System') { + $obj = $RT::System; } else { - die; - push (@results, loc("System Error"). ': '. + push (@results, loc("System Error"). loc("Rights could not be revoked for [_1]", $object_type)); next; } @@ -1017,17 +953,6 @@ sub ProcessCustomFieldUpdates { my ( $err, $msg ) = $Object->DeleteValue($id); push ( @results, $msg ); } - - my $vals = $Object->Values(); - while (my $cfv = $vals->Next()) { - if (my $so = $ARGSRef->{ 'CustomField-' . $Object->Id . '-SortOrder' . $cfv->Id }) { - if ($cfv->SortOrder != $so) { - my ( $err, $msg ) = $cfv->SetSortOrder($so); - push ( @results, $msg ); - } - } - } - return (@results); } @@ -1125,11 +1050,8 @@ sub ProcessTicketCustomFieldUpdates { # For each of those tickets foreach my $tick ( keys %custom_fields_to_mod ) { - my $Ticket = $args{'TicketObj'}; - if (!$Ticket or $Ticket->id != $tick) { - $Ticket = RT::Ticket->new( $session{'CurrentUser'} ); - $Ticket->Load($tick); - } + my $Ticket = RT::Ticket->new( $session{'CurrentUser'} ); + $Ticket->Load($tick); # For each custom field foreach my $cf ( keys %{ $custom_fields_to_mod{$tick} } ) { @@ -1152,10 +1074,10 @@ sub ProcessTicketCustomFieldUpdates { my @values = ( ref( $ARGSRef->{$arg} ) eq 'ARRAY' ) ? @{ $ARGSRef->{$arg} } - : split /\n/, $ARGSRef->{$arg} ; + : ( $ARGSRef->{$arg} ); if ( ( $arg =~ /-AddValue$/ ) || ( $arg =~ /-Value$/ ) ) { foreach my $value (@values) { - next unless length($value); + next unless ($value); my ( $val, $msg ) = $Ticket->AddCustomFieldValue( Field => $cf, Value => $value @@ -1165,7 +1087,7 @@ sub ProcessTicketCustomFieldUpdates { } elsif ( $arg =~ /-DeleteValues$/ ) { foreach my $value (@values) { - next unless length($value); + next unless ($value); my ( $val, $msg ) = $Ticket->DeleteCustomFieldValue( Field => $cf, Value => $value @@ -1178,7 +1100,7 @@ sub ProcessTicketCustomFieldUpdates { my %values_hash; foreach my $value (@values) { - next unless length($value); + next unless ($value); # build up a hash of values that the new set has $values_hash{$value} = 1; diff --git a/rt/lib/RT/Record.pm b/rt/lib/RT/Record.pm index 7a8690618..6962221ea 100755 --- a/rt/lib/RT/Record.pm +++ b/rt/lib/RT/Record.pm @@ -211,10 +211,7 @@ sub LoadByCols { $newhash{$key} = $hash{$key}; } else { - my ($op, $val); - ($key, $op, $val) = $self->_Handle->_MakeClauseCaseInsensitive($key, '=', $hash{$key}); - $newhash{$key}->{operator} = $op; - $newhash{$key}->{value} = $val; + $newhash{ "lower(" . $key . ")" } = lc( $hash{$key} ); } } diff --git a/rt/lib/RT/URI/freeside.pm b/rt/lib/RT/URI/freeside.pm new file mode 100644 index 000000000..bfb514df8 --- /dev/null +++ b/rt/lib/RT/URI/freeside.pm @@ -0,0 +1,188 @@ +# BEGIN LICENSE BLOCK +# +# Copyright (c) 2004 Kristian Hoffmann <khoff@fire2wire.com> +# Based on the original RT::URI::base and RT::URI::fsck_com_rt. +# +# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com> +# +# (Except where explictly superceded by other copyright notices) +# +# 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. +# +# Unless otherwise specified, all modifications, corrections or +# extensions to this work which alter its source code become the +# property of Best Practical Solutions, LLC when submitted for +# inclusion in the work. +# +# +# END LICENSE BLOCK +package RT::URI::freeside; + +use RT::URI::base; +use strict; +use vars qw(@ISA); + +@ISA = qw/RT::URI::base/; + + +=head1 NAME + +RT::URI::base + +=head1 DESCRIPTION + +URI handler for freeside URIs. See http://www.sisd.com/freeside/ for +more information on freeside. + +=cut + + +sub FreesideURIPrefix { + + my $self = shift; + return($self->Scheme . '://freeside'); + +} + +sub FreesideURILabel { + + my $self = shift; + + return(undef) unless (exists($self->{'fstable'}) and + exists($self->{'fspkey'})); + + my $label; + my ($table, $pkey) = ($self->{'fstable'}, $self->{'fspkey'}); + + eval { + use FS::UID qw(dbh); + use FS::Record qw(qsearchs qsearch dbdef); + eval "use FS::$table;"; + use FS::cust_svc; + + my $dbdef = dbdef or die "No dbdef"; + my $pkeyfield = $dbdef->table($table)->primary_key + or die "No primary key for table $table"; + + my $rec = qsearchs($table, { $pkeyfield => $pkey }) + or die "Record with $pkeyfield == $pkey does not exist in table $table"; + + if ($table =~ /^svc_/) { + if ($rec->can('cust_svc')) { + my $cust_svc = $rec->cust_svc or die '$rec->cust_svc failed'; + my ($svc, $tag, $svcdb) = $cust_svc->label; + $label = "Freeside service ${svc}: ${tag}"; + } + } elsif ($table eq 'cust_main') { + my ($last, $first, $company) = map { $rec->getfield($_) } + qw(last first company); + $label = "Freeside customer ${last}, ${first}"; + $label .= ($company ne '') ? " with ${company}" : ''; + } else { + $label = "Freeside ${table}, ${pkeyfield} == ${pkey}"; + } + + #... other cases + + }; + + if ($label and !$@) { + return($label); + } else { + return(undef); + } + + +} + +sub ParseURI { + my $self = shift; + my $uri = shift; + my ($table, $pkey); + + my $uriprefix = $self->FreesideURIPrefix; + if ($uri =~ /^$uriprefix\/(\w+)\/(\d+)$/) { + $table = $1; + $pkey = $2; + $self->{'scheme'} = $self->Scheme; + } else { + return(undef); + } + + $self->{'uri'} = "${uriprefix}/${table}/${pkey}"; + $self->{'fstable'} = $table; + $self->{'fspkey'} = $pkey; + + my $p; + + eval { + use FS::UID qw(dbh); + use FS::CGI qw(popurl); + + if (dbh) { + $p = popurl(3); + } + + }; + + if ($@ or (!$p)) { + $self->{'href'} = $self->{'uri'}; + } else { + $self->{'href'} = "${p}view/${table}.cgi?${pkey}"; + } + + $self->{'uri'}; + +} + +sub Scheme { + my $self = shift; + return('freeside'); + +} + +sub HREF { + my $self = shift; + return($self->{'href'} || $self->{'uri'}); +} + +sub IsLocal { + my $self = shift; + return undef; +} + +=head2 AsString + +Return a "pretty" string representing the URI object. + +This is meant to be used like this: + + % $re = $uri->Resolver; + <A HREF="<% $re->HREF %>"><% $re->AsString %></A> + +=cut + +sub AsString { + my $self = shift; + my $prettystring; + if ($prettystring = $self->FreesideURILabel) { + return $prettystring; + } else { + return $self->URI; + } +} + +eval "require RT::URI::base_Vendor"; +die $@ if ($@ && $@ !~ qr{^Can't locate RT/URI/base_Vendor.pm}); +eval "require RT::URI::base_Local"; +die $@ if ($@ && $@ !~ qr{^Can't locate RT/URI/base_Local.pm}); + +1; diff --git a/rt/sbin/rt-setup-database b/rt/sbin/rt-setup-database index 58f882f6e..17819886c 100644 --- a/rt/sbin/rt-setup-database +++ b/rt/sbin/rt-setup-database @@ -110,6 +110,9 @@ elsif ( $args{'action'} eq 'drop' ) { } drop_db(); } +elsif ( $args{'action'} eq 'insert_initial' ) { + insert_initial_data(); +} elsif ( $args{'action'} eq 'insert' ) { insert_data( $args{'datafile'} ); } @@ -589,6 +592,8 @@ $0: Set up RT's database --action init Initialize the database drop Drop the database. This will ERASE ALL YOUR DATA + insert_initial + Insert RT's core system objects insert Insert data into RT's database. By default, will use RT's installation data. To use a local or supplementary datafile, specify it diff --git a/rt/sbin/rt-setup-database.in b/rt/sbin/rt-setup-database.in index a7ee86d5d..56f4e8763 100644 --- a/rt/sbin/rt-setup-database.in +++ b/rt/sbin/rt-setup-database.in @@ -110,6 +110,9 @@ elsif ( $args{'action'} eq 'drop' ) { } drop_db(); } +elsif ( $args{'action'} eq 'insert_initial' ) { + insert_initial_data(); +} elsif ( $args{'action'} eq 'insert' ) { insert_data( $args{'datafile'} ); } @@ -450,8 +453,6 @@ sub insert_data { $princ->LoadUserDefinedGroup( $item->{'GroupId'} ); } elsif ($item->{'GroupDomain'} eq 'SystemInternal') { $princ->LoadSystemInternalGroup( $item->{'GroupType'} ); - } elsif ($item->{'GroupDomain'} eq 'RT::System-Role') { - $princ->LoadSystemRoleGroup( $item->{'GroupType'} ); } elsif ($item->{'GroupDomain'} eq 'RT::Queue-Role' && $item->{'Queue'}) { $princ->LoadQueueRoleGroup( Type => $item->{'GroupType'}, @@ -591,6 +592,8 @@ $0: Set up RT's database --action init Initialize the database drop Drop the database. This will ERASE ALL YOUR DATA + insert_initial + Insert RT's core system objects insert Insert data into RT's database. By default, will use RT's installation data. To use a local or supplementary datafile, specify it |