summaryrefslogtreecommitdiff
path: root/rt/bin
diff options
context:
space:
mode:
authorIvan Kohler <ivan@freeside.biz>2015-07-09 22:18:55 -0700
committerIvan Kohler <ivan@freeside.biz>2015-07-09 22:27:04 -0700
commite131b1f71f08b69abb832c1687d1f29682d171f8 (patch)
tree490167e41d9fe05b760e7b21a96ee35a86f8edda /rt/bin
parentd05d7346bb2387fd9d0354923d577275c5c7f019 (diff)
RT 4.2.11, ticket#13852
Diffstat (limited to 'rt/bin')
-rwxr-xr-xrt/bin/rt242
-rw-r--r--rt/bin/rt-crontool.in71
-rw-r--r--rt/bin/rt-mailgate.in50
-rw-r--r--rt/bin/rt.in242
4 files changed, 320 insertions, 285 deletions
diff --git a/rt/bin/rt b/rt/bin/rt
index 6d46c34..9c0ed8d 100755
--- a/rt/bin/rt
+++ b/rt/bin/rt
@@ -70,16 +70,6 @@ use Term::ReadLine;
use Time::Local; # used in prettyshow
use File::Temp;
-# strong (GSSAPI based) authentication is supported if the server does provide
-# it and the perl modules GSSAPI and LWP::Authen::Negotiate are installed
-# it can be suppressed by setting externalauth=0 (default is undef)
-eval { require GSSAPI };
-my $no_strong_auth = 'missing perl module GSSAPI';
-if ( ! $@ ) {
- eval {require LWP::Authen::Negotiate};
- $no_strong_auth = $@ ? 'missing perl module LWP::Authen::Negotiate' : 0;
-}
-
# 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.
@@ -99,18 +89,16 @@ my %config = (
queue => undef,
# to protect against unlimited searches a better choice would be
# queue => 'Unknown_Queue',
-# setting externalauth => undef will try GSSAPI auth if the corresponding perl
-# modules are installed, externalauth => 0 is the backward compatible choice
- externalauth => 0,
+ auth => "rt",
),
config_from_file($ENV{RTCONFIG} || ".rtrc"),
config_from_env()
);
+
+$config{auth} = "basic" if delete $config{externalauth};
+
my $session = Session->new("$HOME/.rt_sessions");
my $REST = "$config{server}/REST/1.0";
-$no_strong_auth = 'switched off by externalauth=0'
- if defined $config{externalauth};
-
my $prompt = 'rt> ';
@@ -330,32 +318,53 @@ sub list {
$data{orderby} =~ s/^\+?(.*)/-$1/;
}
- if (!defined $q) {
- $q = $config{query};
+ $type ||= "ticket";
+
+ if (!defined $q ) {
+ if ( $type eq 'ticket' ) {
+ $q = $config{query};
+ }
+ else {
+ $q = '';
+ }
}
-
- $q =~ s/^#//; # get rid of leading hash
- if ($q =~ /^\d+$/) {
- # only digits, must be an id, formulate a correct query
- $q = "id=$q" if $q =~ /^\d+$/;
- } else {
- # a string only, take it as an owner or requestor (quoting done later)
- $q = "(Owner=$q or Requestor like $q) and $config{query}"
- if $q =~ /^[\w\-]+$/;
- # always add a query for a specific queue or (comma separated) queues
- $queue =~ s/,/ or Queue=/g if $queue;
- $q .= " and (Queue=$queue)" if $queue and $q and $q !~ /Queue\s*=/i
- and $q !~ /id\s*=/i;
+
+ if ( $type ne 'ticket' ) {
+ $rawprint = 1;
}
- # correctly quote strings in a query
- $q =~ s/(=|like\s)\s*([^'\d\s]\S*)\b/$1\'$2\'/g;
- $type ||= "ticket";
- unless ($type && defined $q) {
+ unless (defined $q) {
my $item = $type ? "query string" : "object type";
whine "No $item specified.";
$bad = 1;
}
+
+ $q =~ s/^#//; # get rid of leading hash
+ if ( $type eq 'ticket' ) {
+ if ( $q =~ /^\d+$/ ) {
+
+ # only digits, must be an id, formulate a correct query
+ $q = "id=$q" if $q =~ /^\d+$/;
+ }
+ else {
+
+ # a string only, take it as an owner or requestor (quoting done later)
+ $q = "(Owner=$q or Requestor like $q) and $config{query}"
+ if $q =~ /^[\w\-]+$/;
+
+ # always add a query for a specific queue or (comma separated) queues
+ $queue =~ s/,/ or Queue=/g if $queue;
+ $q .= " and (Queue=$queue)"
+ if $queue
+ and $q
+ and $q !~ /Queue\s*=/i
+ and $q !~ /id\s*=/i;
+ }
+
+ # correctly quote strings in a query
+ $q =~ s/(=|like\s)\s*([^'\d\s]\S*)\b/$1\'$2\'/g;
+ }
+
#return help("list", $type) if $bad;
return suggest_help("list", $type, $bad) if $bad;
@@ -772,6 +781,7 @@ sub comment {
my ($action) = @_;
my (%data, $id, @files, @bcc, @cc, $msg, $content_type, $wtime, $edit);
my $bad = 0;
+ my $status = '';
while (@ARGV) {
$_ = shift @ARGV;
@@ -779,7 +789,7 @@ sub comment {
if (/^-e$/) {
$edit = 1;
}
- elsif (/^-(?:[abcmw]|ct)$/) {
+ elsif (/^-(?:[abcmws]|ct)$/) {
unless (@ARGV) {
whine "No argument specified with $_.";
$bad = 1; last;
@@ -795,6 +805,9 @@ sub comment {
elsif (/-ct/) {
$content_type = shift @ARGV;
}
+ elsif (/-s/) {
+ $status = shift @ARGV;
+ }
elsif (/-([bc])/) {
my $a = $_ eq "-b" ? \@bcc : \@cc;
@$a = split /\s*,\s*/, shift @ARGV;
@@ -837,9 +850,12 @@ sub comment {
TimeWorked => $wtime || '',
'Content-Type' => $content_type || 'text/plain',
Text => $msg || '',
- Status => ''
+ Status => $status
}
];
+ if ($status ne '') {
+ push(@{$form->[1]}, "Status");
+ }
my $text = Form::compose([ $form ]);
@@ -1036,20 +1052,23 @@ sub submit {
# Should we send authentication information to start a new session?
my $how = $config{server} =~ /^https/ ? 'over SSL' : 'unencrypted';
my($server) = $config{server} =~ m{^.*//([^/]+)};
- if ($config{externalauth}) {
+
+ if ($config{auth} eq "gssapi") {
+ die "GSSAPI support not available; failed to load perl module GSSAPI:\n$@\n"
+ unless eval { require GSSAPI; 1 };
+ die "GSSAPI support not available; failed to load perl module LWP::Authen::Negotiate:\n$@\n"
+ unless eval { require LWP::Authen::Negotiate; 1 };
+ } elsif ($config{auth} eq "basic") {
+ print " Password will be sent to $server $how\n",
+ " Press CTRL-C now if you do not want to continue\n"
+ if ! $config{passwd};
$h->authorization_basic($config{user}, $config{passwd} || read_passwd() );
+ } elsif ( !defined $session->cookie ) {
print " Password will be sent to $server $how\n",
" Press CTRL-C now if you do not want to continue\n"
if ! $config{passwd};
- } elsif ( $no_strong_auth ) {
- if (!defined $session->cookie) {
- print " Strong encryption not available, $no_strong_auth\n",
- " Password will be sent to $server $how\n",
- " Press CTRL-C now if you do not want to continue\n"
- if ! $config{passwd};
- push @$data, ( user => $config{user} );
- push @$data, ( pass => $config{passwd} || read_passwd() );
- }
+ push @$data, ( user => $config{user} );
+ push @$data, ( pass => $config{passwd} || read_passwd() );
}
# Now, we construct the request.
@@ -1060,9 +1079,7 @@ sub submit {
$req = GET($uri);
}
$session->add_cookie_header($req);
- if ($config{externalauth}) {
- $req->header(%$h);
- }
+ $req->header(%$h) if %$h;
# Then we send the request and parse the response.
DEBUG(3, $req->as_string);
@@ -1400,7 +1417,7 @@ sub Form::compose {
sub config_from_env {
my %env;
- foreach my $k (qw(EXTERNALAUTH DEBUG USER PASSWD SERVER QUERY ORDERBY)) {
+ foreach my $k (qw(EXTERNALAUTH AUTH DEBUG USER PASSWD SERVER QUERY ORDERBY)) {
if (exists $ENV{"RT$k"}) {
$env{lc $k} = $ENV{"RT$k"};
@@ -1454,7 +1471,7 @@ sub parse_config_file {
chomp;
next if (/^#/ || /^\s*$/);
- if (/^(externalauth|user|passwd|server|query|orderby|queue)\s+(.*)\s?$/) {
+ if (/^(externalauth|auth|user|passwd|server|query|orderby|queue)\s+(.*)\s?$/) {
$cfg{$1} = $2;
}
else {
@@ -1563,49 +1580,56 @@ sub vpush {
}
}
+# WARNING: this code is duplicated in lib/RT/Interface/REST.pm
+# If you change one, change both functions at once
# "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+$//;
- my ( $a, $b ) = split /\s*,\s*/, $line, 2;
-
- while ($a) {
- no warnings 'uninitialized';
- if ( $a =~ /^'/ ) {
- my $s = $a;
- while ( $a !~ /'$/ || ( $a !~ /(\\\\)+'$/
- && $a =~ /(\\)+'$/ )) {
- ( $a, $b ) = split /\s*,\s*/, $b, 2;
- $s .= ',' . $a;
- }
- push @words, $s;
- }
- elsif ( $a =~ /^q\{/ ) {
- my $s = $a;
- while ( $a !~ /\}$/ ) {
- ( $a, $b ) =
- split /\s*,\s*/, $b, 2;
- $s .= ',' . $a;
- }
- $s =~ s/^q\{/'/;
- $s =~ s/\}/'/;
- push @words, $s;
+ my ($val, $strip) = @_;
+ my @words;
+ my @values = map {split /\n/} (ref $val eq 'ARRAY' ? @$val : $val);
+
+ foreach my $line (@values) {
+ while ($line =~ /\S/) {
+ $line =~ s/^
+ \s* # Trim leading whitespace
+ (?:
+ (") # Quoted string
+ ((?>[^\\"]*(?:\\.[^\\"]*)*))"
+ |
+ (') # Single-quoted string
+ ((?>[^\\']*(?:\\.[^\\']*)*))'
+ |
+ q\{(.*?)\} # A perl-ish q{} string; this does
+ # no paren balancing, however, and
+ # only exists for back-compat
+ |
+ (.*?) # Anything else, until the next comma
+ )
+ \s* # Trim trailing whitespace
+ (?:
+ \Z # Finish at end-of-line
+ |
+ , # Or a comma
+ )
+ //xs or last; # There should be no way this match
+ # fails, but add a failsafe to
+ # prevent infinite-looping if it
+ # somehow does.
+ my ($quote, $quoted) = ($1 ? ($1, $2) : $3 ? ($3, $4) : ('', $5 || $6));
+ # Only unquote the quote character, or the backslash -- and
+ # only if we were originally quoted..
+ if ($5) {
+ $quoted =~ s/([\\'])/\\$1/g;
+ $quote = "'";
}
- else {
- push @words, $a;
+ if ($strip) {
+ $quoted =~ s/\\([\\$quote])/$1/g if $quote;
+ push @words, $quoted;
+ } else {
+ push @words, "$quote$quoted$quote";
}
- ( $a, $b ) = split /\s*,\s*/, $b, 2;
}
-
-
}
-
return \@words;
}
@@ -1892,15 +1916,17 @@ Text:
The following directives may occur, one per line:
- - server <URL> URL to RT server.
- - user <username> RT username.
- - passwd <passwd> RT user's password.
- - query <RT Query> Default RT Query for list action
- - orderby <order> Default RT order for list action
- - queue <queuename> Default RT Queue for list action
- - externalauth <0|1> Use HTTP Basic authentication
- explicitely setting externalauth to 0 inhibits also GSSAPI based
- authentication, if LWP::Authen::Negotiate (and GSSAPI) is installed
+ - server <URL> URL to RT server.
+ - user <username> RT username.
+ - passwd <passwd> RT user's password.
+ - query <RT Query> Default RT Query for list action
+ - orderby <order> Default RT order for list action
+ - queue <queuename> Default RT Queue for list action
+ - auth <rt|basic|gssapi> Method to authenticate via; "basic"
+ means HTTP Basic authentication, "gssapi" means
+ Kerberos credentials, if your RT is configured
+ with $WebRemoteUserAuth. For backwards
+ compatibility, "externalauth 1" means "auth basic"
Blank and #-commented lines are ignored.
@@ -1919,7 +1945,7 @@ Text:
- RTUSER
- RTPASSWD
- - RTEXTERNALAUTH
+ - RTAUTH
- RTSERVER
- RTDEBUG Numeric debug level. (Set to 3 for full logs.)
- RTCONFIG Specifies a name other than ".rtrc" for the
@@ -2199,13 +2225,14 @@ Text:
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
- RT. (For more information, see "rt help query".) The query string
- must be supplied as one argument.
+ The query string must be supplied as one argument.
+
+ if on tickets, query is in the SQL-like syntax used internally by
+ RT. (For more information, see "rt help query".), otherwise, query
+ is plain string with format "FIELD OP VALUE", e.g. "Name = General".
- (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.)
+ if query string is absent, we limit to privileged ones on users and
+ user defined ones on groups automatically.
Options:
@@ -2236,6 +2263,9 @@ Text:
rt ls -t ticket "Subject like '[PATCH]%'"
rt ls -q systems
rt ls -f owner,subject
+ rt ls -t queue 'Name = General'
+ rt ls -t user 'EmailAddress like foo@bar.com'
+ rt ls -t group 'Name like foo'
--
@@ -2378,6 +2408,8 @@ Text:
than once to attach multiple files.)
-c <addrs> A comma-separated list of Cc addresses.
-b <addrs> A comma-separated list of Bcc addresses.
+ -s <status> Set a new status for the ticket (default will
+ leave the status unchanged)
-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.
diff --git a/rt/bin/rt-crontool.in b/rt/bin/rt-crontool.in
index 383014f..a8fc30d 100644
--- a/rt/bin/rt-crontool.in
+++ b/rt/bin/rt-crontool.in
@@ -51,23 +51,15 @@ use warnings;
use Carp;
# fix lib paths, some may be relative
-BEGIN {
+BEGIN { # BEGIN RT CMD BOILERPLATE
require File::Spec;
+ require Cwd;
my @libs = ("@RT_LIB_PATH@", "@LOCAL_LIB_PATH@");
my $bin_path;
for my $lib (@libs) {
unless ( File::Spec->file_name_is_absolute($lib) ) {
- unless ($bin_path) {
- if ( File::Spec->file_name_is_absolute(__FILE__) ) {
- $bin_path = ( File::Spec->splitpath(__FILE__) )[1];
- }
- else {
- require FindBin;
- no warnings "once";
- $bin_path = $FindBin::Bin;
- }
- }
+ $bin_path ||= ( File::Spec->splitpath(Cwd::abs_path(__FILE__)) )[1];
$lib = File::Spec->catfile( $bin_path, File::Spec->updir, $lib );
}
unshift @INC, $lib;
@@ -79,10 +71,7 @@ use RT;
use Getopt::Long;
-use RT::Interface::CLI qw(CleanEnv GetCurrentUser GetMessageContent loc);
-
-#Clean out all the nasties from the environment
-CleanEnv();
+use RT::Interface::CLI qw(GetCurrentUser loc);
my ( $search, $condition, $action, $search_arg, $condition_arg, $action_arg,
$template, $template_id, $transaction, $transaction_type, $help, $log, $verbose );
@@ -106,7 +95,7 @@ GetOptions(
RT::LoadConfig();
# adjust logging to the screen according to options
-RT->Config->Set( LogToScreen => $log ) if $log;
+RT->Config->Set( LogToSTDERR => $log ) if $log;
#Connect to the database and get RT::SystemUser and RT::Nobody loaded
RT::Init();
@@ -121,7 +110,7 @@ my $CurrentUser = GetCurrentUser();
help() if $help;
unless ( $CurrentUser->Id ) {
- print loc("No RT user found. Please consult your RT administrator.");
+ print loc("No RT user found. Please consult your RT administrator.") . "\n";
exit(1);
}
@@ -261,24 +250,20 @@ sub get_transactions {
#
# =cut
-{ my $cache = undef;
sub get_template {
my $ticket = shift;
return undef unless $template;
unless ( $template =~ /\D/ ) {
# by id
- return $cache if $cache;
-
- my $cache = RT::Template->new( RT->SystemUser );
- $cache->Load( $template );
+ my $template_obj = RT::Template->new( RT->SystemUser );
+ $template_obj->Load( $template );
die "Failed to load template '$template'"
- unless $cache->id;
- return $cache;
+ unless $template_obj->id;
+ return $template_obj;
}
my $queue = $ticket->Queue;
- return $cache->{ $queue } if $cache->{ $queue };
my $res = RT::Template->new( RT->SystemUser );
$res->LoadQueueTemplate( Queue => $queue, Name => $template );
@@ -287,8 +272,8 @@ sub get_template {
die "Failed to load template '$template', either for queue #$queue or global"
unless $res->id;
}
- return $cache->{ $queue } = $res;
-} }
+ return $res;
+}
# =head2 load_module
@@ -299,9 +284,9 @@ sub get_template {
sub load_module {
my $modname = shift;
- eval "require $modname";
- if ($@) {
- die loc( "Failed to load module [_1]. ([_2])", $modname, $@ );
+ unless ($modname->require) {
+ my $error = $@;
+ die loc( "Failed to load module [_1]. ([_2])", $modname, $error );
}
}
@@ -313,37 +298,37 @@ sub help {
. "\n";
print loc("It takes several arguments:") . "\n\n";
- print " "
+ print " "
. loc( "[_1] - Specify the search module you want to use", "--search" )
. "\n";
- print " "
+ print " "
. loc( "[_1] - An argument to pass to [_2]", "--search-arg", "--search" )
. "\n";
- print " "
+ print " "
. loc( "[_1] - Specify the condition module you want to use", "--condition" )
. "\n";
- print " "
+ print " "
. loc( "[_1] - An argument to pass to [_2]", "--condition-arg", "--condition" )
. "\n";
- print " "
+ print " "
. loc( "[_1] - Specify the action module you want to use", "--action" )
. "\n";
- print " "
+ print " "
. loc( "[_1] - An argument to pass to [_2]", "--action-arg", "--action" )
. "\n";
- print " "
+ print " "
. loc( "[_1] - Specify name or id of template(s) you want to use", "--template" )
. "\n";
- print " "
+ print " "
. loc( "[_1] - Specify if you want to use either 'first', 'last' or 'all' transactions", "--transaction" )
. "\n";
- print " "
+ print " "
. loc( "[_1] - Specify the comma separated list of transactions' types you want to use", "--transaction-type" )
. "\n";
- print " "
- . loc( "[_1] - Adjust LogToScreen config option", "--log" ) . "\n";
- print " "
+ print " "
+ . loc( "[_1] - Adjust LogToSTDERR config option", "--log" ) . "\n";
+ print " "
. loc( "[_1] - Output status updates to STDOUT", "--verbose" ) . "\n";
print "\n";
print "\n";
@@ -458,7 +443,7 @@ Specify the comma separated list of transactions' types you want to use
=item log
-Adjust LogToScreen config option
+Adjust LogToSTDERR config option
=item verbose
diff --git a/rt/bin/rt-mailgate.in b/rt/bin/rt-mailgate.in
index 0516bf7..dd0cf3d 100644
--- a/rt/bin/rt-mailgate.in
+++ b/rt/bin/rt-mailgate.in
@@ -144,12 +144,6 @@ sub validate_cli_flags {
return $self->permfail();
}
- if (($opts->{'ca-file'} or $opts->{"verify-ssl"})
- and not LWP::UserAgent->can("ssl_opts")) {
- print STDERR "Verifying SSL certificates requires LWP::UserAgent 6.0 or higher.\n";
- return $self->tempfail();
- }
-
$opts->{"verify-ssl"} = 1 unless defined $opts->{"verify-ssl"};
}
@@ -157,13 +151,12 @@ sub get_useragent {
my $self = shift;
my $opts = shift;
my $ua = LWP::UserAgent->new();
+ $ua->agent("rt-mailgate/@RT_VERSION_MAJOR@.@RT_VERSION_MINOR@.@RT_VERSION_PATCH@ ");
$ua->cookie_jar( { file => $opts->{'jar'} } ) if $opts->{'jar'};
- if ( $ua->can("ssl_opts") ) {
- $ua->ssl_opts( verify_hostname => $opts->{'verify-ssl'} );
- $ua->ssl_opts( SSL_ca_file => $opts->{'ca-file'} )
- if $opts->{'ca-file'};
- }
+ $ua->ssl_opts( verify_hostname => $opts->{'verify-ssl'} );
+ $ua->ssl_opts( SSL_ca_file => $opts->{'ca-file'} )
+ if $opts->{'ca-file'};
return $ua;
}
@@ -226,6 +219,14 @@ sub upload_message {
$ua->timeout( exists( $opts->{'timeout'} ) ? $opts->{'timeout'} : 180 );
my $r = $ua->post( $full_url, $post_params, Content_Type => 'form-data' );
+
+ # Follow 3 redirects
+ my $n = 0;
+ while ($n++ < 3 and $r->is_redirect) {
+ $full_url = $r->header( "Location" );
+ $r = $ua->post( $full_url, $post_params, Content_Type => 'form-data' );
+ }
+
$self->check_failure($r);
my $content = $r->content;
@@ -252,13 +253,8 @@ sub check_failure {
my $r = shift;
return if $r->is_success;
- # XXX TODO 4.2: Remove the multi-line error strings in favor of something more concise
- print STDERR <<" ERROR";
-An Error Occurred
-=================
-
-@{[ $r->status_line ]}
- ERROR
+ print STDERR "HTTP request failed: @{[ $r->status_line ]}. "
+ ."Your webserver logs may have more information or there may be a network problem.\n";
print STDERR "\n$0: undefined server error\n" if $opts->{'debug'};
return $self->tempfail();
}
@@ -358,10 +354,6 @@ is found.
This flag tells the mail gateway where it can find your RT server. You should
probably use the same URL that users use to log into RT.
-If your RT server uses SSL, you will need to install additional Perl
-libraries. RT will detect and install these dependencies if you pass the
-C<--enable-ssl-mailgate> flag to configure as documented in RT's README.
-
If you have a self-signed SSL certificate, you may also need to pass
C<--ca-file> or C<--no-verify-ssl>, below.
@@ -382,9 +374,6 @@ of CA. This is required if you have a self-signed certificate, or some
other certificate which is not traceable back to an certificate your
system ultimitely trusts.
-Verifying SSL certificates requires L<LWP::UserAgent> version 6.0 or
-higher; explicitly passing C<--verify-ssl> on prior versions will error.
-
=item C<--extension> OPTIONAL
Some MTAs will route mail sent to user-foo@host or user+foo@host to user@host
@@ -420,19 +409,16 @@ equivalent.
=head1 SETUP
Much of the set up of the mail gateway depends on your MTA and mail
-routing configuration. However, you will need first of all to create an
-RT user for the mail gateway and assign it a password; this helps to
-ensure that mail coming into the web server did originate from the
-gateway.
+routing configuration.
-Next, you need to route mail to C<rt-mailgate> for the queues you're
+You need to route mail to C<rt-mailgate> for the queues you're
monitoring. For instance, if you're using F</etc/aliases> and you have a
"bugs" queue, you will want something like this:
- bugs: "|/opt/rt4/bin/rt-mailgate --queue bugs --action correspond
+ bugs: "|@RT_BIN_PATH_R@/rt-mailgate --queue bugs --action correspond
--url http://rt.mycorp.com/"
- bugs-comment: "|/opt/rt4/bin/rt-mailgate --queue bugs --action comment
+ bugs-comment: "|@RT_BIN_PATH_R@/rt-mailgate --queue bugs --action comment
--url http://rt.mycorp.com/"
Note that you don't have to run your RT server on your mail server, as
diff --git a/rt/bin/rt.in b/rt/bin/rt.in
index 60eed68..ee60033 100644
--- a/rt/bin/rt.in
+++ b/rt/bin/rt.in
@@ -70,16 +70,6 @@ use Term::ReadLine;
use Time::Local; # used in prettyshow
use File::Temp;
-# strong (GSSAPI based) authentication is supported if the server does provide
-# it and the perl modules GSSAPI and LWP::Authen::Negotiate are installed
-# it can be suppressed by setting externalauth=0 (default is undef)
-eval { require GSSAPI };
-my $no_strong_auth = 'missing perl module GSSAPI';
-if ( ! $@ ) {
- eval {require LWP::Authen::Negotiate};
- $no_strong_auth = $@ ? 'missing perl module LWP::Authen::Negotiate' : 0;
-}
-
# 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.
@@ -99,18 +89,16 @@ my %config = (
queue => undef,
# to protect against unlimited searches a better choice would be
# queue => 'Unknown_Queue',
-# setting externalauth => undef will try GSSAPI auth if the corresponding perl
-# modules are installed, externalauth => 0 is the backward compatible choice
- externalauth => 0,
+ auth => "rt",
),
config_from_file($ENV{RTCONFIG} || ".rtrc"),
config_from_env()
);
+
+$config{auth} = "basic" if delete $config{externalauth};
+
my $session = Session->new("$HOME/.rt_sessions");
my $REST = "$config{server}/REST/1.0";
-$no_strong_auth = 'switched off by externalauth=0'
- if defined $config{externalauth};
-
my $prompt = 'rt> ';
@@ -330,32 +318,53 @@ sub list {
$data{orderby} =~ s/^\+?(.*)/-$1/;
}
- if (!defined $q) {
- $q = $config{query};
+ $type ||= "ticket";
+
+ if (!defined $q ) {
+ if ( $type eq 'ticket' ) {
+ $q = $config{query};
+ }
+ else {
+ $q = '';
+ }
}
-
- $q =~ s/^#//; # get rid of leading hash
- if ($q =~ /^\d+$/) {
- # only digits, must be an id, formulate a correct query
- $q = "id=$q" if $q =~ /^\d+$/;
- } else {
- # a string only, take it as an owner or requestor (quoting done later)
- $q = "(Owner=$q or Requestor like $q) and $config{query}"
- if $q =~ /^[\w\-]+$/;
- # always add a query for a specific queue or (comma separated) queues
- $queue =~ s/,/ or Queue=/g if $queue;
- $q .= " and (Queue=$queue)" if $queue and $q and $q !~ /Queue\s*=/i
- and $q !~ /id\s*=/i;
+
+ if ( $type ne 'ticket' ) {
+ $rawprint = 1;
}
- # correctly quote strings in a query
- $q =~ s/(=|like\s)\s*([^'\d\s]\S*)\b/$1\'$2\'/g;
- $type ||= "ticket";
- unless ($type && defined $q) {
+ unless (defined $q) {
my $item = $type ? "query string" : "object type";
whine "No $item specified.";
$bad = 1;
}
+
+ $q =~ s/^#//; # get rid of leading hash
+ if ( $type eq 'ticket' ) {
+ if ( $q =~ /^\d+$/ ) {
+
+ # only digits, must be an id, formulate a correct query
+ $q = "id=$q" if $q =~ /^\d+$/;
+ }
+ else {
+
+ # a string only, take it as an owner or requestor (quoting done later)
+ $q = "(Owner=$q or Requestor like $q) and $config{query}"
+ if $q =~ /^[\w\-]+$/;
+
+ # always add a query for a specific queue or (comma separated) queues
+ $queue =~ s/,/ or Queue=/g if $queue;
+ $q .= " and (Queue=$queue)"
+ if $queue
+ and $q
+ and $q !~ /Queue\s*=/i
+ and $q !~ /id\s*=/i;
+ }
+
+ # correctly quote strings in a query
+ $q =~ s/(=|like\s)\s*([^'\d\s]\S*)\b/$1\'$2\'/g;
+ }
+
#return help("list", $type) if $bad;
return suggest_help("list", $type, $bad) if $bad;
@@ -772,6 +781,7 @@ sub comment {
my ($action) = @_;
my (%data, $id, @files, @bcc, @cc, $msg, $content_type, $wtime, $edit);
my $bad = 0;
+ my $status = '';
while (@ARGV) {
$_ = shift @ARGV;
@@ -779,7 +789,7 @@ sub comment {
if (/^-e$/) {
$edit = 1;
}
- elsif (/^-(?:[abcmw]|ct)$/) {
+ elsif (/^-(?:[abcmws]|ct)$/) {
unless (@ARGV) {
whine "No argument specified with $_.";
$bad = 1; last;
@@ -795,6 +805,9 @@ sub comment {
elsif (/-ct/) {
$content_type = shift @ARGV;
}
+ elsif (/-s/) {
+ $status = shift @ARGV;
+ }
elsif (/-([bc])/) {
my $a = $_ eq "-b" ? \@bcc : \@cc;
@$a = split /\s*,\s*/, shift @ARGV;
@@ -837,9 +850,12 @@ sub comment {
TimeWorked => $wtime || '',
'Content-Type' => $content_type || 'text/plain',
Text => $msg || '',
- Status => ''
+ Status => $status
}
];
+ if ($status ne '') {
+ push(@{$form->[1]}, "Status");
+ }
my $text = Form::compose([ $form ]);
@@ -1036,20 +1052,23 @@ sub submit {
# Should we send authentication information to start a new session?
my $how = $config{server} =~ /^https/ ? 'over SSL' : 'unencrypted';
my($server) = $config{server} =~ m{^.*//([^/]+)};
- if ($config{externalauth}) {
+
+ if ($config{auth} eq "gssapi") {
+ die "GSSAPI support not available; failed to load perl module GSSAPI:\n$@\n"
+ unless eval { require GSSAPI; 1 };
+ die "GSSAPI support not available; failed to load perl module LWP::Authen::Negotiate:\n$@\n"
+ unless eval { require LWP::Authen::Negotiate; 1 };
+ } elsif ($config{auth} eq "basic") {
+ print " Password will be sent to $server $how\n",
+ " Press CTRL-C now if you do not want to continue\n"
+ if ! $config{passwd};
$h->authorization_basic($config{user}, $config{passwd} || read_passwd() );
+ } elsif ( !defined $session->cookie ) {
print " Password will be sent to $server $how\n",
" Press CTRL-C now if you do not want to continue\n"
if ! $config{passwd};
- } elsif ( $no_strong_auth ) {
- if (!defined $session->cookie) {
- print " Strong encryption not available, $no_strong_auth\n",
- " Password will be sent to $server $how\n",
- " Press CTRL-C now if you do not want to continue\n"
- if ! $config{passwd};
- push @$data, ( user => $config{user} );
- push @$data, ( pass => $config{passwd} || read_passwd() );
- }
+ push @$data, ( user => $config{user} );
+ push @$data, ( pass => $config{passwd} || read_passwd() );
}
# Now, we construct the request.
@@ -1060,9 +1079,7 @@ sub submit {
$req = GET($uri);
}
$session->add_cookie_header($req);
- if ($config{externalauth}) {
- $req->header(%$h);
- }
+ $req->header(%$h) if %$h;
# Then we send the request and parse the response.
DEBUG(3, $req->as_string);
@@ -1400,7 +1417,7 @@ sub Form::compose {
sub config_from_env {
my %env;
- foreach my $k (qw(EXTERNALAUTH DEBUG USER PASSWD SERVER QUERY ORDERBY)) {
+ foreach my $k (qw(EXTERNALAUTH AUTH DEBUG USER PASSWD SERVER QUERY ORDERBY)) {
if (exists $ENV{"RT$k"}) {
$env{lc $k} = $ENV{"RT$k"};
@@ -1454,7 +1471,7 @@ sub parse_config_file {
chomp;
next if (/^#/ || /^\s*$/);
- if (/^(externalauth|user|passwd|server|query|orderby|queue)\s+(.*)\s?$/) {
+ if (/^(externalauth|auth|user|passwd|server|query|orderby|queue)\s+(.*)\s?$/) {
$cfg{$1} = $2;
}
else {
@@ -1563,49 +1580,56 @@ sub vpush {
}
}
+# WARNING: this code is duplicated in lib/RT/Interface/REST.pm
+# If you change one, change both functions at once
# "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+$//;
- my ( $a, $b ) = split /\s*,\s*/, $line, 2;
-
- while ($a) {
- no warnings 'uninitialized';
- if ( $a =~ /^'/ ) {
- my $s = $a;
- while ( $a !~ /'$/ || ( $a !~ /(\\\\)+'$/
- && $a =~ /(\\)+'$/ )) {
- ( $a, $b ) = split /\s*,\s*/, $b, 2;
- $s .= ',' . $a;
- }
- push @words, $s;
- }
- elsif ( $a =~ /^q\{/ ) {
- my $s = $a;
- while ( $a !~ /\}$/ ) {
- ( $a, $b ) =
- split /\s*,\s*/, $b, 2;
- $s .= ',' . $a;
- }
- $s =~ s/^q\{/'/;
- $s =~ s/\}/'/;
- push @words, $s;
+ my ($val, $strip) = @_;
+ my @words;
+ my @values = map {split /\n/} (ref $val eq 'ARRAY' ? @$val : $val);
+
+ foreach my $line (@values) {
+ while ($line =~ /\S/) {
+ $line =~ s/^
+ \s* # Trim leading whitespace
+ (?:
+ (") # Quoted string
+ ((?>[^\\"]*(?:\\.[^\\"]*)*))"
+ |
+ (') # Single-quoted string
+ ((?>[^\\']*(?:\\.[^\\']*)*))'
+ |
+ q\{(.*?)\} # A perl-ish q{} string; this does
+ # no paren balancing, however, and
+ # only exists for back-compat
+ |
+ (.*?) # Anything else, until the next comma
+ )
+ \s* # Trim trailing whitespace
+ (?:
+ \Z # Finish at end-of-line
+ |
+ , # Or a comma
+ )
+ //xs or last; # There should be no way this match
+ # fails, but add a failsafe to
+ # prevent infinite-looping if it
+ # somehow does.
+ my ($quote, $quoted) = ($1 ? ($1, $2) : $3 ? ($3, $4) : ('', $5 || $6));
+ # Only unquote the quote character, or the backslash -- and
+ # only if we were originally quoted..
+ if ($5) {
+ $quoted =~ s/([\\'])/\\$1/g;
+ $quote = "'";
}
- else {
- push @words, $a;
+ if ($strip) {
+ $quoted =~ s/\\([\\$quote])/$1/g if $quote;
+ push @words, $quoted;
+ } else {
+ push @words, "$quote$quoted$quote";
}
- ( $a, $b ) = split /\s*,\s*/, $b, 2;
}
-
-
}
-
return \@words;
}
@@ -1892,15 +1916,17 @@ Text:
The following directives may occur, one per line:
- - server <URL> URL to RT server.
- - user <username> RT username.
- - passwd <passwd> RT user's password.
- - query <RT Query> Default RT Query for list action
- - orderby <order> Default RT order for list action
- - queue <queuename> Default RT Queue for list action
- - externalauth <0|1> Use HTTP Basic authentication
- explicitely setting externalauth to 0 inhibits also GSSAPI based
- authentication, if LWP::Authen::Negotiate (and GSSAPI) is installed
+ - server <URL> URL to RT server.
+ - user <username> RT username.
+ - passwd <passwd> RT user's password.
+ - query <RT Query> Default RT Query for list action
+ - orderby <order> Default RT order for list action
+ - queue <queuename> Default RT Queue for list action
+ - auth <rt|basic|gssapi> Method to authenticate via; "basic"
+ means HTTP Basic authentication, "gssapi" means
+ Kerberos credentials, if your RT is configured
+ with $WebRemoteUserAuth. For backwards
+ compatibility, "externalauth 1" means "auth basic"
Blank and #-commented lines are ignored.
@@ -1919,7 +1945,7 @@ Text:
- RTUSER
- RTPASSWD
- - RTEXTERNALAUTH
+ - RTAUTH
- RTSERVER
- RTDEBUG Numeric debug level. (Set to 3 for full logs.)
- RTCONFIG Specifies a name other than ".rtrc" for the
@@ -2199,13 +2225,14 @@ Text:
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
- RT. (For more information, see "rt help query".) The query string
- must be supplied as one argument.
+ The query string must be supplied as one argument.
+
+ if on tickets, query is in the SQL-like syntax used internally by
+ RT. (For more information, see "rt help query".), otherwise, query
+ is plain string with format "FIELD OP VALUE", e.g. "Name = General".
- (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.)
+ if query string is absent, we limit to privileged ones on users and
+ user defined ones on groups automatically.
Options:
@@ -2236,6 +2263,9 @@ Text:
rt ls -t ticket "Subject like '[PATCH]%'"
rt ls -q systems
rt ls -f owner,subject
+ rt ls -t queue 'Name = General'
+ rt ls -t user 'EmailAddress like foo@bar.com'
+ rt ls -t group 'Name like foo'
--
@@ -2378,6 +2408,8 @@ Text:
than once to attach multiple files.)
-c <addrs> A comma-separated list of Cc addresses.
-b <addrs> A comma-separated list of Bcc addresses.
+ -s <status> Set a new status for the ticket (default will
+ leave the status unchanged)
-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.