diff options
author | Ivan Kohler <ivan@freeside.biz> | 2015-07-09 22:18:55 -0700 |
---|---|---|
committer | Ivan Kohler <ivan@freeside.biz> | 2015-07-09 22:27:04 -0700 |
commit | e131b1f71f08b69abb832c1687d1f29682d171f8 (patch) | |
tree | 490167e41d9fe05b760e7b21a96ee35a86f8edda /rt/bin | |
parent | d05d7346bb2387fd9d0354923d577275c5c7f019 (diff) |
RT 4.2.11, ticket#13852
Diffstat (limited to 'rt/bin')
-rwxr-xr-x | rt/bin/rt | 242 | ||||
-rw-r--r-- | rt/bin/rt-crontool.in | 71 | ||||
-rw-r--r-- | rt/bin/rt-mailgate.in | 50 | ||||
-rw-r--r-- | rt/bin/rt.in | 242 |
4 files changed, 320 insertions, 285 deletions
@@ -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. |