This commit was generated by cvs2svn to compensate for changes in r12472,
[freeside.git] / rt / bin / rt.in
index b44e041..aefe7af 100644 (file)
@@ -1,27 +1,53 @@
 #!@PERL@ -w
 #!@PERL@ -w
-# BEGIN LICENSE BLOCK
-# 
-# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
-# 
-# (Except where explictly superceded by other copyright notices)
-# 
+# BEGIN BPS TAGGED BLOCK {{{
+#
+# COPYRIGHT:
+#
+# This software is Copyright (c) 1996-2011 Best Practical Solutions, LLC
+#                                          <sales@bestpractical.com>
+#
+# (Except where explicitly superseded by other copyright notices)
+#
+#
+# LICENSE:
+#
 # This work is made available to you under the terms of Version 2 of
 # the GNU General Public License. A copy of that license should have
 # been provided with this software, but in any event can be snarfed
 # from www.gnu.org.
 # This work is 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.
 # 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
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+#
+#
+# CONTRIBUTION SUBMISSION POLICY:
+#
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
+#
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
+#
+# END BPS TAGGED BLOCK }}}
+# Designed and implemented for Best Practical Solutions, LLC by
+# Abhijit Menon-Sen <ams@wiw.org>
 
 use strict;
 
 
 use strict;
 
@@ -30,7 +56,21 @@ use strict;
 
 use Cwd;
 use LWP;
 
 use Cwd;
 use LWP;
+use Text::ParseWords;
 use HTTP::Request::Common;
 use HTTP::Request::Common;
+use HTTP::Headers;
+use Term::ReadLine;
+use Time::Local; # used in prettyshow
+
+# 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).
 
 # We derive configuration information from hardwired defaults, dotfiles,
 # and the RT* environment variables (in increasing order of precedence).
@@ -42,18 +82,29 @@ my $HOME = eval{(getpwuid($<))[7]}
            || ".";
 my %config = (
     (
            || ".";
 my %config = (
     (
-        debug   => 0,
-        user    => eval{(getpwuid($<))[0]} || $ENV{USER} || $ENV{USERNAME},
-        passwd  => undef,
-        server  => 'http://localhost/rt/',
-        query   => undef,
-        orderby => undef,
+        debug        => 0,
+        user         => eval{(getpwuid($<))[0]} || $ENV{USER} || $ENV{USERNAME},
+        passwd       => undef,
+        server       => 'http://localhost/',
+        query        => "Status!='resolved' and Status!='rejected'",
+        orderby      => 'id',
+        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,
     ),
     config_from_file($ENV{RTCONFIG} || ".rtrc"),
     config_from_env()
 );
 my $session = new Session("$HOME/.rt_sessions");
 my $REST = "$config{server}/REST/1.0";
     ),
     config_from_file($ENV{RTCONFIG} || ".rtrc"),
     config_from_env()
 );
 my $session = new Session("$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> ';
 
 sub whine;
 sub DEBUG { warn @_ if $config{debug} >= shift }
 
 sub whine;
 sub DEBUG { warn @_ if $config{debug} >= shift }
@@ -61,11 +112,12 @@ 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.)
 
 # 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+';
+my $name    = '[\w.-]+';
+my $CF_name = '[\sa-z0-9_ :()/-]+';
+my $field   = '(?i:[a-z][a-z0-9_-]*|C(?:ustom)?F(?:ield)?-'.$CF_name.'|CF\.\{'.$CF_name.'\})';
+my $label   = '[a-zA-Z0-9@_.+-]+';
+my $labels  = "(?:$label,)*$label";
+my $idlist  = '(?:(?:\d+-)?\d+,)*(?:\d+-)?\d+';
 
 # Our command line looks like this:
 #
 
 # Our command line looks like this:
 #
@@ -77,6 +129,7 @@ my $idlist = '(?:(?:\d+-)?\d+,)*(?:\d+-)?\d+';
 my %handlers = (
 #   handler     => [ ...aliases... ],
     version     => ["version", "ver"],
 my %handlers = (
 #   handler     => [ ...aliases... ],
     version     => ["version", "ver"],
+    shell       => ["shell"],
     logout      => ["logout"],
     help        => ["help", "man"],
     show        => ["show", "cat"],
     logout      => ["logout"],
     help        => ["help", "man"],
     show        => ["show", "cat"],
@@ -86,48 +139,87 @@ my %handlers = (
     link        => ["link", "ln"],
     merge       => ["merge"],
     grant       => ["grant", "revoke"],
     link        => ["link", "ln"],
     merge       => ["merge"],
     grant       => ["grant", "revoke"],
+    take        => ["take", "steal", "untake"],
+    quit        => ["quit", "exit"],
+    setcommand  => ["del", "delete", "give", "res", "resolve",
+                    "subject"],
 );
 
 );
 
-# Once we find and call an appropriate handler, we're done.
-
-my (%actions, $action);
+my %actions;
 foreach my $fn (keys %handlers) {
     foreach my $alias (@{ $handlers{$fn} }) {
         $actions{$alias} = \&{"$fn"};
     }
 }
 foreach my $fn (keys %handlers) {
     foreach my $alias (@{ $handlers{$fn} }) {
         $actions{$alias} = \&{"$fn"};
     }
 }
-if (@ARGV && exists $actions{$ARGV[0]}) {
-    $action = shift @ARGV;
+
+# Once we find and call an appropriate handler, we're done.
+
+sub handler {
+    my $action;
+
+    push @ARGV, 'shell' if (!@ARGV);    # default to shell mode
+    shift @ARGV if ($ARGV[0] eq 'rt');    # ignore a leading 'rt'
+    if (@ARGV && exists $actions{$ARGV[0]}) {
+        $action = shift @ARGV;
+        return $actions{$action}->($action);
+    }
+    else {
+        print STDERR "rt: Unknown command '@ARGV'.\n";
+        print STDERR "rt: For help, run 'rt help'.\n";
+        return 1;
+    }
 }
 }
-$actions{$action || "help"}->($action || ());
-exit;
+
+exit handler();
 
 # Handler functions.
 # ------------------
 #
 # The following subs are handlers for each entry in %actions.
 
 
 # Handler functions.
 # ------------------
 #
 # The following subs are handlers for each entry in %actions.
 
+sub shell {
+    $|=1;
+    my $term = new Term::ReadLine 'RT CLI';
+    while ( defined ($_ = $term->readline($prompt)) ) {
+        next if /^#/ || /^\s*$/;
+
+        @ARGV = shellwords($_);
+        handler();
+    }
+}
+
 sub version {
     print "rt $VERSION\n";
 sub version {
     print "rt $VERSION\n";
+    return 0;
 }
 
 sub logout {
     submit("$REST/logout") if defined $session->cookie;
 }
 
 sub logout {
     submit("$REST/logout") if defined $session->cookie;
+    return 0;
+}
+
+sub quit {
+    logout();
+    exit;
 }
 
 }
 
+my %help;
 sub help {
 sub help {
-    my ($action, $type) = @_;
-    my (%help, $key);
+    my ($action, $type, $rv) = @_;
+    $rv = defined $rv ? $rv : 0;
+    my $key;
 
     # What help topics do we know about?
 
     # 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;
+    if (!%help) {
+        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};
             }
 
             foreach $title (grep $_, @titles) {
                 $help{$title} = $item->[2]{Text};
             }
+        }
     }
 
     # What does the user want help with?
     }
 
     # What does the user want help with?
@@ -163,16 +255,22 @@ sub help {
     }
 
     print STDERR $help{$key}, "\n\n";
     }
 
     print STDERR $help{$key}, "\n\n";
+    return $rv;
 }
 
 # Displays a list of objects that match some specified condition.
 
 sub list {
 }
 
 # Displays a list of objects that match some specified condition.
 
 sub list {
-    my ($q, $type, %data, $orderby);
+    my ($q, $type, %data);
+    my $orderby = $config{orderby};
+    
     if ($config{orderby}) {
          $data{orderby} = $config{orderby};
     } 
     my $bad = 0;
     if ($config{orderby}) {
          $data{orderby} = $config{orderby};
     } 
     my $bad = 0;
+    my $rawprint = 0;
+    my $reverse_sort = 0;
+    my $queue = $config{queue};
 
     while (@ARGV) {
         $_ = shift @ARGV;
 
     while (@ARGV) {
         $_ = shift @ARGV;
@@ -188,6 +286,13 @@ sub list {
         }
         elsif (/^-([isl])$/) {
             $data{format} = $1;
         }
         elsif (/^-([isl])$/) {
             $data{format} = $1;
+            $rawprint = 1;
+        }
+        elsif (/^-q$/) {
+            $queue = shift @ARGV;
+        }
+        elsif (/^-r$/) {
+            $reverse_sort = 1;
         }
         elsif (/^-f$/) {
             if ($ARGV[0] !~ /^(?:(?:$field,)*$field)$/) {
         }
         elsif (/^-f$/) {
             if ($ARGV[0] !~ /^(?:(?:$field,)*$field)$/) {
@@ -195,6 +300,8 @@ sub list {
                 $bad = 1; last;
             }
             $data{fields} = shift @ARGV;
                 $bad = 1; last;
             }
             $data{fields} = shift @ARGV;
+            $data{format} = 's' if ! $data{format};
+            $rawprint = 1;
         }
         elsif (!defined $q && !/^-/) {
             $q = $_;
         }
         elsif (!defined $q && !/^-/) {
             $q = $_;
@@ -205,21 +312,53 @@ sub list {
             $bad = 1; last;
         }
     }
             $bad = 1; last;
         }
     }
-    
+    if ( ! $rawprint and ! exists $data{format} ) {
+        $data{format} = 'l';
+    }
+    if ( $reverse_sort and $data{orderby} =~ /^-/ ) {
+        $data{orderby} =~ s/^-/+/;
+    } elsif ($reverse_sort) {
+        $data{orderby} =~ s/^\+?(.*)/-$1/;
+    }
+
     if (!defined $q) {
         $q = $config{query}; 
     }
     
     if (!defined $q) {
         $q = $config{query}; 
     }
     
+    $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;
+    }
+    # correctly quote strings in a query
+    $q =~ s/(=|like\s)\s*([^'\d\s]\S*)\b/$1\'$2\'/g;
+
     $type ||= "ticket";
     unless ($type && defined $q) {
         my $item = $type ? "query string" : "object type";
         whine "No $item specified.";
         $bad = 1;
     }
     $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;
+    #return help("list", $type) if $bad;
+    return suggest_help("list", $type, $bad) if $bad;
 
 
+    print "Query:$q\n" if ! $rawprint;
     my $r = submit("$REST/search/$type", { query => $q, %data });
     my $r = submit("$REST/search/$type", { query => $q, %data });
-    print $r->content;
+    if ( $rawprint ) {
+        print $r->content;
+    } else {
+        my $forms = Form::parse($r->content);
+        prettylist ($forms);
+    }
+    return 0;
 }
 
 # Displays selected information about a single object.
 }
 
 # Displays selected information about a single object.
@@ -228,10 +367,12 @@ sub show {
     my ($type, @objects, %data);
     my $slurped = 0;
     my $bad = 0;
     my ($type, @objects, %data);
     my $slurped = 0;
     my $bad = 0;
+    my $rawprint = 0;
+    my $histspec;
 
     while (@ARGV) {
         $_ = shift @ARGV;
 
     while (@ARGV) {
         $_ = shift @ARGV;
-
+        s/^#// if /^#\d+/; # get rid of leading hash
         if (/^-t$/) {
             $bad = 1, last unless defined($type = get_type_argument());
         }
         if (/^-t$/) {
             $bad = 1, last unless defined($type = get_type_argument());
         }
@@ -240,6 +381,7 @@ sub show {
         }
         elsif (/^-([isl])$/) {
             $data{format} = $1;
         }
         elsif (/^-([isl])$/) {
             $data{format} = $1;
+            $rawprint = 1;
         }
         elsif (/^-$/ && !$slurped) {
             chomp(my @lines = <STDIN>);
         }
         elsif (/^-$/ && !$slurped) {
             chomp(my @lines = <STDIN>);
@@ -258,9 +400,21 @@ sub show {
                 $bad = 1; last;
             }
             $data{fields} = shift @ARGV;
                 $bad = 1; last;
             }
             $data{fields} = shift @ARGV;
+            # option f requires short raw listing format
+            $data{format} = 's';
+            $rawprint = 1;
+        }
+        elsif (/^\d+$/ and my $spc2 = is_object_spec("ticket/$_", $type)) {
+            push @objects, $spc2;
+            $histspec = is_object_spec("ticket/$_/history", $type);
+        }
+        elsif (/^\d+\// and my $spc3 = is_object_spec("ticket/$_", $type)) {
+            push @objects, $spc3;
+            $rawprint = 1 if $_ =~ /\/content$/;
         }
         elsif (my $spec = is_object_spec($_, $type)) {
             push @objects, $spec;
         }
         elsif (my $spec = is_object_spec($_, $type)) {
             push @objects, $spec;
+            $rawprint = 1 if $_ =~ /\/content$/ or $_ !~ /^ticket/;
         }
         else {
             my $datum = /^-/ ? "option" : "argument";
         }
         else {
             my $datum = /^-/ ? "option" : "argument";
@@ -268,15 +422,36 @@ sub show {
             $bad = 1; last;
         }
     }
             $bad = 1; last;
         }
     }
+    if ( ! $rawprint ) {
+        push @objects, $histspec if $histspec;
+        $data{format} = 'l' if ! exists $data{format};
+    }
 
     unless (@objects) {
         whine "No objects specified.";
         $bad = 1;
     }
 
     unless (@objects) {
         whine "No objects specified.";
         $bad = 1;
     }
-    return help("show", $type) if $bad;
+    #return help("show", $type) if $bad;
+    return suggest_help("show", $type, $bad) if $bad;
 
     my $r = submit("$REST/show", { id => \@objects, %data });
 
     my $r = submit("$REST/show", { id => \@objects, %data });
-    print $r->content;
+    my $c = $r->content;
+    # if this isn't a text reply, remove the trailing newline so we
+    # don't corrupt things like tarballs when people do
+    # show ticket/id/attachments/id/content > foo.tar.gz
+    if ($r->content_type !~ /^text\//) {
+        chomp($c);
+        $rawprint = 1;
+    }
+    if ( $rawprint ) {
+        print $c;
+    } else {
+        # I do not know how to get more than one form correctly returned
+        $c =~ s!^RT/[\d\.]+ 200 Ok$!--!mg;
+        my $forms = Form::parse($c);
+        prettyshow ($forms);
+    }
+    return 0;
 }
 
 # To create a new object, we ask the server for a form with the defaults
 }
 
 # To create a new object, we ask the server for a form with the defaults
@@ -298,6 +473,7 @@ sub edit {
     
     while (@ARGV) {
         $_ = shift @ARGV;
     
     while (@ARGV) {
         $_ = shift @ARGV;
+        s/^#// if /^#\d+/; # get rid of leading hash
 
         if    (/^-e$/) { $edit = 1 }
         elsif (/^-i$/) { $input = 1 }
 
         if    (/^-e$/) { $edit = 1 }
         elsif (/^-i$/) { $input = 1 }
@@ -322,7 +498,7 @@ sub edit {
         elsif (/^set$/i) {
             my $vars = 0;
 
         elsif (/^set$/i) {
             my $vars = 0;
 
-            while (@ARGV && $ARGV[0] =~ /^($field)([+-]?=)(.*)$/) {
+            while (@ARGV && $ARGV[0] =~ /^($field)([+-]?=)(.*)$/s) {
                 my ($key, $op, $val) = ($1, $2, $3);
                 my $hash = ($op eq '=') ? \%set : ($op =~ /^\+/) ? \%add : \%del;
 
                 my ($key, $op, $val) = ($1, $2, $3);
                 my $hash = ($op eq '=') ? \%set : ($op =~ /^\+/) ? \%add : \%del;
 
@@ -340,7 +516,7 @@ sub edit {
             my $vars = 0;
             my $hash = ($_ eq "add") ? \%add : \%del;
 
             my $vars = 0;
             my $hash = ($_ eq "add") ? \%add : \%del;
 
-            while (@ARGV && $ARGV[0] =~ /^($field)=(.*)$/) {
+            while (@ARGV && $ARGV[0] =~ /^($field)=(.*)$/s) {
                 my ($key, $val) = ($1, $2);
 
                 vpush($hash, lc $key, $val);
                 my ($key, $val) = ($1, $2);
 
                 vpush($hash, lc $key, $val);
@@ -353,6 +529,9 @@ sub edit {
             }
             $cl = $vars;
         }
             }
             $cl = $vars;
         }
+        elsif (/^\d+$/ and my $spc2 = is_object_spec("ticket/$_", $type)) {
+            push @objects, $spc2;
+        }
         elsif (my $spec = is_object_spec($_, $type)) {
             push @objects, $spec;
         }
         elsif (my $spec = is_object_spec($_, $type)) {
             push @objects, $spec;
         }
@@ -378,20 +557,25 @@ sub edit {
             whine "What type of object do you want to create?";
             $bad = 1;
         }
             whine "What type of object do you want to create?";
             $bad = 1;
         }
-        @objects = ("$type/new");
+        @objects = ("$type/new") if defined($type);
     }
     }
-    return help($action, $type) if $bad;
+    #return help($action, $type) if $bad;
+    return suggest_help($action, $type, $bad) 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
 
     # 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.
+    # specifies only simple variable assignments.  We *should* get a
+    # form if we're creating a new ticket, so that the default values
+    # get filled in properly.
+
+    my @new_objects = grep /\/new$/, @objects;
 
     if ($input) {
         local $/ = undef;
         $text = <STDIN>;
     }
 
     if ($input) {
         local $/ = undef;
         $text = <STDIN>;
     }
-    elsif ($edit || %add || %del || !$cl) {
+    elsif ($edit || %add || %del || !$cl || @new_objects) {
         my $r = submit("$REST/show", { id => \@objects, format => 'l' });
         $text = $r->content;
     }
         my $r = submit("$REST/show", { id => \@objects, format => 'l' });
         $text = $r->content;
     }
@@ -460,7 +644,7 @@ sub edit {
 
     if ($output) {
         print $text;
 
     if ($output) {
         print $text;
-        exit;
+        return 0;
     }
 
     my $synerr = 0;
     }
 
     my $synerr = 0;
@@ -486,11 +670,60 @@ EDIT:
             }
             else {
                 print $r->content;
             }
             else {
                 print $r->content;
-                exit -1;
+                return 0;
             }
         }
         print $r->content;
     }
             }
         }
         print $r->content;
     }
+    return 0;
+}
+
+# handler for special edit commands. A valid edit command is constructed and
+# further work is delegated to the edit handler
+
+sub setcommand {
+    my ($action) = @_;
+    my ($id, $bad, $what);
+    if ( @ARGV ) {
+        $_ = shift @ARGV;
+        $id = $1 if (m|^(?:ticket/)?($idlist)$|);
+    }
+    if ( ! $id ) {
+        $bad = 1;
+        whine "No ticket number specified.";
+    }
+    if ( @ARGV ) {
+        if ($action eq 'subject') {
+            my $subject = '"'.join (" ", @ARGV).'"';
+            @ARGV = ();
+            $what = "subject=$subject";
+        } elsif ($action eq 'give') {
+            my $owner = shift @ARGV;
+            $what = "owner=$owner";
+        }
+    } else {
+        if ( $action eq 'delete' or $action eq 'del' ) {
+            $what = "status=deleted";
+        } elsif ($action eq 'resolve' or $action eq 'res' ) {
+            $what = "status=resolved";
+        } elsif ($action eq 'take' ) {
+            $what = "owner=$config{user}";
+        } elsif ($action eq 'untake') {
+            $what = "owner=Nobody";
+        }
+    }
+    if (@ARGV) {
+        $bad = 1;
+        whine "Extraneous arguments for action $action: @ARGV.";
+    }
+    if ( ! $what ) {
+        $bad = 1;
+        whine "unrecognized action $action.";
+    }
+    return help("edit", undef, $bad) if $bad;
+    @ARGV = ( $id, "set", $what );
+    print "Executing: rt edit @ARGV\n";
+    return edit("edit");
 }
 
 # We roll "comment" and "correspond" into the same handler.
 }
 
 # We roll "comment" and "correspond" into the same handler.
@@ -515,7 +748,7 @@ sub comment {
             if (/-a/) {
                 unless (-f $ARGV[0] && -r $ARGV[0]) {
                     whine "Cannot read attachment: '$ARGV[0]'.";
             if (/-a/) {
                 unless (-f $ARGV[0] && -r $ARGV[0]) {
                     whine "Cannot read attachment: '$ARGV[0]'.";
-                    exit -1;
+                    return 0;
                 }
                 push @files, shift @ARGV;
             }
                 }
                 push @files, shift @ARGV;
             }
@@ -547,7 +780,8 @@ sub comment {
         whine "No object specified.";
         $bad = 1;
     }
         whine "No object specified.";
         $bad = 1;
     }
-    return help($action, "ticket") if $bad;
+    #return help($action, "ticket") if $bad;
+    return suggest_help($action, "ticket") if $bad;
 
     my $form = [
         "",
 
     my $form = [
         "",
@@ -560,6 +794,7 @@ sub comment {
             Attachment => [ @files ],
             TimeWorked => $wtime || '',
             Text       => $msg || '',
             Attachment => [ @files ],
             TimeWorked => $wtime || '',
             Text       => $msg || '',
+            Status => ''
         }
     ];
 
         }
     ];
 
@@ -571,7 +806,7 @@ sub comment {
 
         do {
             my $ntext = vi($text);
 
         do {
             my $ntext = vi($text);
-            exit if ($error && $ntext eq $text);
+            return if ($error && $ntext eq $text);
             $text = $ntext;
             $form = Form::parse($text);
             $error = 0;
             $text = $ntext;
             $form = Form::parse($text);
             $error = 0;
@@ -583,7 +818,7 @@ sub comment {
                 goto NEXT;
             }
             elsif (!@$o) {
                 goto NEXT;
             }
             elsif (!@$o) {
-                exit;
+                return 0;
             }
             @files = @{ vsplit($k->{Attachment}) };
 
             }
             @files = @{ vsplit($k->{Attachment}) };
 
@@ -599,8 +834,9 @@ sub comment {
     }
     $data{content} = $text;
 
     }
     $data{content} = $text;
 
-    my $r = submit("$REST/ticket/comment/$id", \%data);
+    my $r = submit("$REST/ticket/$id/comment", \%data);
     print $r->content;
     print $r->content;
+    return 0;
 }
 
 # Merge one ticket into another.
 }
 
 # Merge one ticket into another.
@@ -611,6 +847,7 @@ sub merge {
 
     while (@ARGV) {
         $_ = shift @ARGV;
 
     while (@ARGV) {
         $_ = shift @ARGV;
+        s/^#// if /^#\d+/; # get rid of leading hash
 
         if (/^\d+$/) {
             push @id, $_;
 
         if (/^\d+$/) {
             push @id, $_;
@@ -626,16 +863,20 @@ sub merge {
         whine "Too $evil arguments specified.";
         $bad = 1;
     }
         whine "Too $evil arguments specified.";
         $bad = 1;
     }
-    return help("merge", "ticket") if $bad;
+    #return help("merge", "ticket") if $bad;
+    return suggest_help("merge", "ticket", $bad) if $bad;
 
 
-    my $r = submit("$REST/ticket/merge/$id[0]", {into => $id[1]});
+    my $r = submit("$REST/ticket/$id[0]/merge/$id[1]");
     print $r->content;
     print $r->content;
+    return 0;
 }
 
 # Link one ticket to another.
 
 sub link {
     my ($bad, $del, %data) = (0, 0, ());
 }
 
 # Link one ticket to another.
 
 sub link {
     my ($bad, $del, %data) = (0, 0, ());
+    my $type;
+
     my %ltypes = map { lc $_ => $_ } qw(DependsOn DependedOnBy RefersTo
                                         ReferredToBy HasMember MemberOf);
 
     my %ltypes = map { lc $_ => $_ } qw(DependsOn DependedOnBy RefersTo
                                         ReferredToBy HasMember MemberOf);
 
@@ -645,21 +886,26 @@ sub link {
         if (/^-d$/) {
             $del = 1;
         }
         if (/^-d$/) {
             $del = 1;
         }
+        elsif (/^-t$/) {
+            $bad = 1, last unless defined($type = get_type_argument());
+        }
         else {
             whine "Unrecognised option: '$_'.";
             $bad = 1; last;
         }
     }
         else {
             whine "Unrecognised option: '$_'.";
             $bad = 1; last;
         }
     }
-
+    
+    $type = "ticket" unless $type; # default type to tickets
+    
     if (@ARGV == 3) {
         my ($from, $rel, $to) = @ARGV;
         if ($from !~ /^\d+$/ || $to !~ /^\d+$/) {
             my $bad = $from =~ /^\d+$/ ? $to : $from;
     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.";
+            whine "Invalid $type ID '$bad' specified.";
             $bad = 1;
         }
             $bad = 1;
         }
-        unless (exists $ltypes{lc $rel}) {
-            whine "Invalid relationship '$rel' specified.";
+        if (($type eq "ticket") && ( ! exists $ltypes{lc $rel})) {
+            whine "Invalid link '$rel' for type $type specified.";
             $bad = 1;
         }
         %data = (id => $from, rel => $rel, to => $to, del => $del);
             $bad = 1;
         }
         %data = (id => $from, rel => $rel, to => $to, del => $del);
@@ -669,10 +915,50 @@ sub link {
         whine "Too $bad arguments specified.";
         $bad = 1;
     }
         whine "Too $bad arguments specified.";
         $bad = 1;
     }
-    return help("link", "ticket") if $bad;
+    return suggest_help("link", $type, $bad) if $bad;
+    my $r = submit("$REST/$type/link", \%data);
+    print $r->content;
+    return 0;
+}
+
+# Take/steal a ticket
+sub take {
+    my ($cmd) = @_;
+    my ($bad, %data) = (0, ());
 
 
-    my $r = submit("$REST/ticket/link", \%data);
+    my $id;
+
+    # get the ticket id
+    if (@ARGV == 1) {
+        ($id) = @ARGV;
+        unless ($id =~ /^\d+$/) {
+            whine "Invalid ticket ID $id specified.";
+            $bad = 1;
+        }
+        my $form = [
+            "",
+            [ "Ticket", "Action" ],
+            {
+                Ticket => $id,
+                Action => $cmd,
+                Status => '',
+            }
+        ];
+
+        my $text = Form::compose([ $form ]);
+        $data{content} = $text;
+    }
+    else {
+        $bad = @ARGV < 1 ? "few" : "many";
+        whine "Too $bad arguments specified.";
+        $bad = 1;
+    }
+    return suggest_help("take", "ticket", $bad) if $bad;
+
+    my $r = submit("$REST/ticket/$id/take", \%data);
     print $r->content;
     print $r->content;
+    return 0;
 }
 
 # Grant/revoke a user's rights.
 }
 
 # Grant/revoke a user's rights.
@@ -685,6 +971,7 @@ sub grant {
     }
 
     $revoke = 1 if $cmd->{action} eq 'revoke';
     }
 
     $revoke = 1 if $cmd->{action} eq 'revoke';
+    return 0;
 }
 
 # Client <-> Server communication.
 }
 
 # Client <-> Server communication.
@@ -698,6 +985,7 @@ sub submit {
     my ($uri, $content) = @_;
     my ($req, $data);
     my $ua = new LWP::UserAgent(agent => "RT/3.0b", env_proxy => 1);
     my ($uri, $content) = @_;
     my ($req, $data);
     my $ua = new LWP::UserAgent(agent => "RT/3.0b", env_proxy => 1);
+    my $h = HTTP::Headers->new;
 
     # Did the caller specify any data to send with the request?
     $data = [];
 
     # Did the caller specify any data to send with the request?
     $data = [];
@@ -723,9 +1011,22 @@ sub submit {
     }
 
     # Should we send authentication information to start a new session?
     }
 
     # 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() );
+    my $how = $config{server} =~ /^https/ ? 'over SSL' : 'unencrypted';
+    (my $server = $config{server}) =~ s/^.*\/\/([^\/]+)\/?/$1/;
+    if ($config{externalauth}) {
+        $h->authorization_basic($config{user}, $config{passwd} || read_passwd() );
+        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() );
+        }
     }
 
     # Now, we construct the request.
     }
 
     # Now, we construct the request.
@@ -736,6 +1037,9 @@ sub submit {
         $req = GET($uri);
     }
     $session->add_cookie_header($req);
         $req = GET($uri);
     }
     $session->add_cookie_header($req);
+    if ($config{externalauth}) {
+        $req->header(%$h);
+    }
 
     # Then we send the request and parse the response.
     DEBUG(3, $req->as_string);
 
     # Then we send the request and parse the response.
     DEBUG(3, $req->as_string);
@@ -752,7 +1056,7 @@ sub submit {
         $text =~ s/\n*$/\n/ if ($text);
 
         # "RT/3.0.1 401 Credentials required"
         $text =~ s/\n*$/\n/ if ($text);
 
         # "RT/3.0.1 401 Credentials required"
-       if ($status !~ m#^RT/\d+(?:\S+) (\d+) ([\w\s]+)$#) {
+        if ($status !~ m#^RT/\d+(?:\S+) (\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;
             warn "rt: Malformed RT response from $config{server}.\n";
             warn "(Rerun with RTDEBUG=3 for details.)\n" if $config{debug} < 3;
             exit -1;
@@ -786,7 +1090,7 @@ sub submit {
             # For anything else, we just die.
             elsif ($res->code != 409) {
                 warn "rt: ", $res->content;
             # For anything else, we just die.
             elsif ($res->code != 409) {
                 warn "rt: ", $res->content;
-                exit;
+                #exit;
             }
         }
     }
             }
         }
     }
@@ -862,7 +1166,7 @@ sub submit {
         $file ||= $self->{file};
         local *F;
 
         $file ||= $self->{file};
         local *F;
 
-        open(F, $file) && do {
+        open(F, '<', $file) && do {
             $self->{file} = $file;
             my $sids = $self->{sids} = {};
             while (<F>) {
             $self->{file} = $file;
             my $sids = $self->{sids} = {};
             while (<F>) {
@@ -883,7 +1187,7 @@ sub submit {
         $file ||= $self->{file};
         local *F;
 
         $file ||= $self->{file};
         local *F;
 
-        open(F, ">$file") && do {
+        open(F, '>', $file) && do {
             my $sids = $self->{sids};
             foreach my $server (keys %$sids) {
                 foreach my $user (keys %{ $sids->{$server} }) {
             my $sids = $self->{sids};
             foreach my $server (keys %$sids) {
                 foreach my $user (keys %{ $sids->{$server} }) {
@@ -921,7 +1225,7 @@ sub submit {
 sub Form::parse {
     my $state = 0;
     my @forms = ();
 sub Form::parse {
     my $state = 0;
     my @forms = ();
-    my @lines = split /\n/, $_[0];
+    my @lines = split /\n/, $_[0] if $_[0];
     my ($c, $o, $k, $e) = ("", [], {}, "");
 
     LINE:
     my ($c, $o, $k, $e) = ("", [], {}, "");
 
     LINE:
@@ -1043,7 +1347,7 @@ sub Form::compose {
                         $line .= ",\n$sp$v";
                     }
                     else {
                         $line .= ",\n$sp$v";
                     }
                     else {
-                        $line = $line ? "$line, $v" : "$key: $v";
+                        $line = $line ? "$line,$v" : "$key: $v";
                     }
                 }
 
                     }
                 }
 
@@ -1077,7 +1381,8 @@ sub Form::compose {
 sub config_from_env {
     my %env;
 
 sub config_from_env {
     my %env;
 
-    foreach my $k ("DEBUG", "USER", "PASSWD", "SERVER", "QUERY", "ORDERBY") {
+    foreach my $k (qw(EXTERNALAUTH DEBUG USER PASSWD SERVER QUERY ORDERBY)) {
+
         if (exists $ENV{"RT$k"}) {
             $env{lc $k} = $ENV{"RT$k"};
         }
         if (exists $ENV{"RT$k"}) {
             $env{lc $k} = $ENV{"RT$k"};
         }
@@ -1110,7 +1415,7 @@ sub config_from_file {
         }
 
         # Still nothing? We'll fall back to some likely defaults.
         }
 
         # Still nothing? We'll fall back to some likely defaults.
-        for ("$HOME/$rc", "/etc/rt.conf") {
+        for ("$HOME/$rc", "@LOCAL_ETC_PATH@/rt.conf", "/etc/rt.conf") {
             return parse_config_file($_) if (-r $_);
         }
     }
             return parse_config_file($_) if (-r $_);
         }
     }
@@ -1122,13 +1427,14 @@ sub config_from_file {
 sub parse_config_file {
     my %cfg;
     my ($file) = @_;
 sub parse_config_file {
     my %cfg;
     my ($file) = @_;
+    local $_; # $_ may be aliased to a constant, from line 1163
 
 
-    open(CFG, $file) && do {
+    open(CFG, '<', $file) && do {
         while (<CFG>) {
             chomp;
             next if (/^#/ || /^\s*$/);
 
         while (<CFG>) {
             chomp;
             next if (/^#/ || /^\s*$/);
 
-            if (/^(user|passwd|server|query|orderby)\s+(.*)\s?$/) {
+            if (/^(externalauth|user|passwd|server|query|orderby|queue)\s+(.*)\s?$/) {
                 $cfg{$1} = $2;
             }
             else {
                 $cfg{$1} = $2;
             }
             else {
@@ -1147,7 +1453,7 @@ sub whine {
     my $sub = (caller(1))[3];
     $sub =~ s/^main:://;
     warn "rt: $sub: @_\n";
     my $sub = (caller(1))[3];
     $sub =~ s/^main:://;
     warn "rt: $sub: @_\n";
-    return;
+    return 0;
 }
 
 sub read_passwd {
 }
 
 sub read_passwd {
@@ -1173,9 +1479,9 @@ sub vi {
     local *F;
     local $/ = undef;
 
     local *F;
     local $/ = undef;
 
-    open(F, ">$file") || die "$file: $!\n"; print F $text; close(F);
+    open(F, '>', $file) or die "$file: $!\n"; print F $text; close(F);
     system($editor, $file) && die "Couldn't run $editor.\n";
     system($editor, $file) && die "Couldn't run $editor.\n";
-    open(F, $file) || die "$file: $!\n"; $text = <F>; close(F);
+    open(F, '<', $file) or die "$file: $!\n"; $text = <F>; close(F);
     unlink($file);
 
     return $text;
     unlink($file);
 
     return $text;
@@ -1205,26 +1511,68 @@ sub vsplit {
     my @values = ref $val eq 'ARRAY' ? @$val : $val;
 
     foreach my $line (map {split /\n/} @values) {
     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.
+        # XXX: This should become a real parser, Ã  la Text::ParseWords.
         $line =~ s/^\s+//;
         $line =~ s/\s+$//;
         $line =~ s/^\s+//;
         $line =~ s/\s+$//;
-        push @words, split /\s*,\s*/, $line;
+        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;
+            }
+            else {
+                push @words, $a;
+            }
+            ( $a, $b ) = split /\s*,\s*/, $b, 2;
+        }
+
+
     }
 
     return \@words;
 }
 
     }
 
     return \@words;
 }
 
+# WARN: this code is duplicated in lib/RT/Interface/REST.pm
+# change both functions at once
 sub expand_list {
     my ($list) = @_;
 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 }
+    my @elts;
+    foreach (split /\s*,\s*/, $list) {
+        push @elts, /^(\d+)-(\d+)$/? ($1..$2): $_;
     }
 
     }
 
-    @elts{@elts}=();
-    return sort {$a<=>$b} keys %elts;
+    return map $_->[0], # schwartzian transform
+        sort {
+            defined $a->[1] && defined $b->[1]?
+                # both numbers
+                $a->[1] <=> $b->[1]
+                :!defined $a->[1] && !defined $b->[1]?
+                    # both letters
+                    $a->[2] cmp $b->[2]
+                    # mix, number must be first
+                    :defined $a->[1]? -1: 1
+        }
+        map [ $_, (defined( /^(\d+)$/ )? $1: undef), lc($_) ],
+        @elts;
 }
 
 sub get_type_argument {
 }
 
 sub get_type_argument {
@@ -1271,7 +1619,130 @@ sub is_object_spec {
 
     $spec =~ s|^(?:$type/)?|$type/| if defined $type;
     return $spec if ($spec =~ m{^$name/(?:$idlist|$labels)(?:/.*)?$}o);
 
     $spec =~ s|^(?:$type/)?|$type/| if defined $type;
     return $spec if ($spec =~ m{^$name/(?:$idlist|$labels)(?:/.*)?$}o);
-    return;
+    return 0;
+}
+
+sub suggest_help {
+    my ($action, $type, $rv) = @_;
+
+    print STDERR "rt: For help, run 'rt help $action'.\n" if defined $action;
+    print STDERR "rt: For help, run 'rt help $type'.\n" if defined $type;
+    return $rv;
+}
+
+sub str2time {
+    # simplified procedure for parsing date, avoid loading Date::Parse
+    my %month = (Jan => 0, Feb => 1, Mar => 2, Apr => 3, May =>  4, Jun =>  5,
+                 Jul => 6, Aug => 7, Sep => 8, Oct => 9, Nov => 10, Dec => 11);
+    $_ = shift;
+    my ($mon, $day, $hr, $min, $sec, $yr, $monstr);
+    if ( /(\w{3})\s+(\d\d?)\s+(\d\d):(\d\d):(\d\d)\s+(\d{4})/ ) {
+        ($monstr, $day, $hr, $min, $sec, $yr) = ($1, $2, $3, $4, $5, $6);
+        $mon = $month{$monstr} if exists $month{$monstr};
+    } elsif ( /(\d{4})-(\d\d)-(\d\d)\s+(\d\d):(\d\d):(\d\d)/ ) {
+        ($yr, $mon, $day, $hr, $min, $sec) = ($1, $2-1, $3, $4, $5, $6);
+    }
+    if ( $yr and defined $mon and $day and defined $hr and defined $sec ) {
+        return timelocal($sec,$min,$hr,$day,$mon,$yr);
+    } else {
+        print "Unknown date format in parsedate: $_\n";
+        return undef;
+    }
+}
+
+sub date_diff {
+    my ($old, $new) = @_;
+    $new = time() if ! $new;
+    $old = str2time($old) if $old !~ /^\d+$/;
+    $new = str2time($new) if $new !~ /^\d+$/;
+    return "???" if ! $old or ! $new;
+
+    my %seconds = (min => 60,
+                   hr  => 60*60,
+                   day => 60*60*24,
+                   wk  => 60*60*24*7,
+                   mth => 60*60*24*30,
+                   yr  => 60*60*24*365);
+
+    my $diff = $new - $old;
+    my $what = 'sec';
+    my $howmuch = $diff;
+    for ( sort {$seconds{$a} <=> $seconds{$b}} keys %seconds) {
+        last if $diff < $seconds{$_};
+        $what = $_;
+        $howmuch = int($diff/$seconds{$_});
+    }
+    return "$howmuch $what";
+}
+
+sub prettyshow {
+    my $forms = shift;
+    my ($form) = grep { exists $_->[2]->{Queue} } @$forms;
+    my $k = $form->[2];
+    # dates are in local time zone
+    if ( $k ) {
+        print "Date: $k->{Created}\n";
+        print "From: $k->{Requestors}\n";
+        print "Cc: $k->{Cc}\n" if $k->{Cc};
+        print "X-AdminCc: $k->{AdminCc}\n" if $k->{AdminCc};
+        print "X-Queue: $k->{Queue}\n";
+        print "Subject: [rt #$k->{id}] $k->{Subject}\n\n";
+    }
+    # dates in these attributes are in GMT and will be converted
+    foreach my $form (@$forms) {
+        my ($c, $o, $k, $e) = @$form;
+        next if ! $k->{id} or exists $k->{Queue};
+        if ( exists $k->{Created} ) {
+            my ($y,$m,$d,$hh,$mm,$ss) = ($k->{Created} =~ /(\d\d\d\d)-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)/);
+            $m--;
+            my $created = localtime(timegm($ss,$mm,$hh,$d,$m,$y));
+            if ( exists $k->{Description} ) {
+                print "===> $k->{Description} on $created\n";
+            }
+        }
+        print "$k->{Content}\n" if exists $k->{Content} and
+                                   $k->{Content} !~ /to have no content$/ and
+                                   $k->{Type} ne 'EmailRecord';
+        print "$k->{Attachments}\n" if exists $k->{Attachments} and
+                                   $k->{Attachments};
+    }
+}
+
+sub prettylist {
+    my $forms = shift;
+    my $heading = "Ticket Owner Queue    Age   Told Status Requestor Subject\n";
+    $heading .= '-' x 80 . "\n";
+    my (@open, @me);
+    foreach my $form (@$forms) {
+        my ($c, $o, $k, $e) = @$form;
+        next if ! $k->{id};
+        print $heading if $heading;
+        $heading = '';
+        my $id = $k->{id};
+        $id =~ s!^ticket/!!;
+        my $owner = $k->{Owner} eq 'Nobody' ? '' : $k->{Owner};
+        $owner = substr($owner, 0, 5);
+        my $queue = substr($k->{Queue}, 0, 5);
+        my $subject = substr($k->{Subject}, 0, 30);
+        my $age = date_diff($k->{Created});
+        my $told = $k->{Told} eq 'Not set' ? '' : date_diff($k->{Told});
+        my $status = substr($k->{Status}, 0, 6);
+        my $requestor = substr($k->{Requestors}, 0, 9);
+        my $line = sprintf "%6s %5s %5s %6s %6s %-6s %-9s %-30s\n",
+            $id, $owner, $queue, $age, $told, $status, $requestor, $subject;
+        if ( $k->{Owner} eq 'Nobody' ) {
+            push @open, $line;
+        } elsif ($k->{Owner} eq $config{user} ) {
+            push @me, $line;
+        } else {
+            print $line;
+        }
+    }
+    print "No matches found\n" if $heading;
+    printf "========== my %2d open tickets ==========\n", scalar @me if @me;
+    print @me if @me;
+    printf "========== %2d unowned tickets ==========\n", scalar @open if @open;
+    print @open if @open;
 }
 
 __DATA__
 }
 
 __DATA__
@@ -1280,10 +1751,7 @@ Title: intro
 Title: introduction
 Text:
 
 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.
+    This is a command-line interface to RT 3.0 or newer.
 
     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
 
     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
@@ -1295,9 +1763,10 @@ Text:
 
     For more information:
 
 
     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 usage         (syntax information)
+        - rt help objects       (how to specify objects)
+        - rt help actions       (a list of possible actions)
+        - rt help types         (a list of object types)
 
         - rt help config        (configuration details)
         - rt help examples      (a few useful examples)
 
         - rt help config        (configuration details)
         - rt help examples      (a few useful examples)
@@ -1312,6 +1781,8 @@ Text:
     Syntax:
 
         rt <action> [options] [arguments]
     Syntax:
 
         rt <action> [options] [arguments]
+      or
+        rt shell
 
     Each invocation of this program must specify an action (e.g. "edit",
     "create"), options to modify behaviour, and other arguments required
 
     Each invocation of this program must specify an action (e.g. "edit",
     "create"), options to modify behaviour, and other arguments required
@@ -1322,6 +1793,10 @@ Text:
     "rt help <action>". Some actions may be referred to by more than one
     name ("create" is the same as "new", for example).  
 
     "rt help <action>". Some actions may be referred to by more than one
     name ("create" is the same as "new", for example).  
 
+    You may also call "rt shell", which will give you an 'rt>' prompt at
+    which you can issue commands of the form "<action> [options] 
+    [arguments]".  See "rt help shell" for details.
+
     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
     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
@@ -1336,6 +1811,7 @@ Text:
         - rt help objects       (how to specify objects)
         - rt help actions       (a list of actions)
         - rt help types         (a list of object types)
         - rt help objects       (how to specify objects)
         - rt help actions       (a list of actions)
         - rt help types         (a list of object types)
+        - rt help shell         (how to use the shell)
 
 --
 
 
 --
 
@@ -1350,7 +1826,8 @@ Text:
     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
     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.
+    files are found, it will also check for ~/.rtrc, @LOCAL_ETC_PATH@/rt.conf
+    and /etc/rt.conf.
 
     Configuration directives:
 
 
     Configuration directives:
 
@@ -1361,9 +1838,21 @@ Text:
         - passwd <passwd>       RT user's password.
         - query <RT Query>      Default RT Query for list action
         - orderby <order>       Default RT order for list action
         - 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
 
         Blank and #-commented lines are ignored.
 
 
         Blank and #-commented lines are ignored.
 
+    Sample configuration file contents:
+
+         server  https://rt.somewhere.com/
+         # more than one queue can be given (by adding a query expression)
+         queue helpdesk or queue=support
+         query Status != resolved and Owner=myaccount
+
+
     Environment variables:
 
         The following environment variables override any corresponding
     Environment variables:
 
         The following environment variables override any corresponding
@@ -1371,6 +1860,7 @@ Text:
 
         - RTUSER
         - RTPASSWD
 
         - RTUSER
         - RTPASSWD
+        - RTEXTERNALAUTH
         - RTSERVER
         - RTDEBUG       Numeric debug level. (Set to 3 for full logs.)
         - RTCONFIG      Specifies a name other than ".rtrc" for the
         - RTSERVER
         - RTDEBUG       Numeric debug level. (Set to 3 for full logs.)
         - RTCONFIG      Specifies a name other than ".rtrc" for the
@@ -1402,8 +1892,12 @@ Text:
     "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-10".
     
     "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-10".
     
+    If just a number is given as object specification it will be
+    interpreted as ticket/<number>
+
     Examples:
 
     Examples:
 
+        1                   # the same as ticket/1
         ticket/1
         ticket/1/attachments
         ticket/1/attachments/3
         ticket/1
         ticket/1/attachments
         ticket/1/attachments/3
@@ -1441,6 +1935,22 @@ Text:
         - rt help <action>      (action-specific details)
         - rt help types         (a list of possible types)
 
         - rt help <action>      (action-specific details)
         - rt help types         (a list of possible types)
 
+    The following actions on tickets are also possible:
+
+        - comment       Add comments to a ticket
+        - correspond    Add comments to a ticket
+        - merge         Merge one ticket into another
+        - link          Link one ticket to another
+        - take          Take a ticket (steal and untake are possible as well)
+
+    For several edit set subcommands that are frequently used abbreviations
+    have been introduced. These abbreviations are:
+
+        - delete or del  delete a ticket           (edit set status=deleted)
+        - resolve or res resolve a ticket          (edit set status=resolved)
+        - subject        change subject of ticket  (edit set subject=string)
+        - give           give a ticket to somebody (edit set owner=user)
+
 --
 
 Title: types
 --
 
 Title: types
@@ -1479,6 +1989,13 @@ Text:
         - merge
         - comment
         - correspond
         - merge
         - comment
         - correspond
+        - take
+        - steal
+        - untake
+        - give
+        - resolve
+        - delete
+        - subject
 
     Attributes:
 
 
     Attributes:
 
@@ -1537,6 +2054,83 @@ Text:
 
 --
 
 
 --
 
+Title: subject
+Text:
+
+    Syntax:
+
+        rt subject <id> <new subject text>
+
+    Change the subject of a ticket whose ticket id is given.
+
+--
+
+Title: give
+Text:
+
+    Syntax:
+
+        rt give <id> <accountname>
+
+    Give a ticket whose ticket id is given to another user.
+
+--
+
+Title: steal
+Text:
+
+        rt steal <id> 
+
+    Steal a ticket whose ticket id is given, i.e. set the owner to myself.
+
+--
+
+Title: take
+Text:
+
+    Syntax:
+
+        rt take <id>
+
+    Take a ticket whose ticket id is given, i.e. set the owner to myself.
+
+--
+
+Title: untake
+Text:
+
+    Syntax:
+
+        rt untake <id>
+
+    Untake a ticket whose ticket id is given, i.e. set the owner to Nobody.
+
+--
+
+Title: resolve
+Title: res
+Text:
+
+    Syntax:
+
+        rt resolve <id>
+
+    Resolves a ticket whose ticket id is given.
+
+--
+
+Title: delete
+Title: del
+Text:
+
+    Syntax:
+
+        rt delete <id>
+
+    Deletes a ticket whose ticket id is given.
+
+--
+
 Title: logout
 Text:
 
 Title: logout
 Text:
 
@@ -1575,24 +2169,30 @@ Text:
         The following options control how much information is displayed
         about each matching object:
 
         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.
+        -i             Numeric IDs only. (Useful for |rt edit -; see examples.)
+        -s             Short description.
+        -l             Longer description.
+        -f <field[s]   Display only the fields listed and the ticket id
 
         In addition,
         
 
         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".)
+        -o +/-<field>  Orders the returned list by the specified field.
+        -r             reversed order (useful if a default was given)
+        -q queue[s]    restricts the query to the queue[s] given
+                       multiple queues are separated by comma
+        -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:
 
 
     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 "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]%'"
         rt ls -i "Priority > 5"|rt edit - set status=resolved
         rt ls -t ticket "Subject like '[PATCH]%'"
+        rt ls -q systems
+        rt ls -f owner,subject
 
 --
 
 
 --
 
@@ -1610,11 +2210,23 @@ Text:
     that refers to the links for tickets 1-3). Consult "rt help <type>"
     and "rt help objects" for further details.
 
     that refers to the links for tickets 1-3). Consult "rt help <type>"
     and "rt help objects" for further details.
 
+    If only a number is given it will be interpreted as the objects
+    ticket/number and ticket/number/history
+
     This command writes a set of forms representing the requested object
     data to STDOUT.
 
     Options:
 
     This command writes a set of forms representing the requested object
     data to STDOUT.
 
     Options:
 
+        The following options control how much information is displayed
+        about each matching object:
+
+        Without any formatting options prettyprinted output is generated.
+        Giving any of the two options below reverts to raw output.
+        -s      Short description (history and attachments only).
+        -l      Longer description (history and attachments only).
+
+        In addition,
         -               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.
         -               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.
@@ -1626,7 +2238,10 @@ Text:
         rt show ticket/3/attachments/29
         rt show ticket/3/attachments/29/content
         rt show ticket/1-3/links
         rt show ticket/3/attachments/29
         rt show ticket/3/attachments/29/content
         rt show ticket/1-3/links
+        rt show ticket/3/history
+        rt show -l ticket/3/history
         rt show -t user 2
         rt show -t user 2
+        rt show 2
 
 --
 
 
 --
 
@@ -1643,6 +2258,8 @@ Text:
 
     Edits information corresponding to the specified objects.
 
 
     Edits information corresponding to the specified objects.
 
+    A purely numeric object id nnn is translated into ticket/nnn
+
     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
     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
@@ -1682,11 +2299,11 @@ Text:
         rt create -t ticket
 
         # Non-interactive.
         rt create -t ticket
 
         # Non-interactive.
-        rt edit ticket/1-3 add cc=foo@example.com set priority=3
+        rt edit ticket/1-3 add cc=foo@example.com set priority=3 due=tomorrow
         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 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 \
+        rt create -t ticket set subject='new ticket' priority=10 \
                             add cc=foo@example.com
 
 --
                             add cc=foo@example.com
 
 --
@@ -1721,7 +2338,7 @@ Text:
 
     Examples:
 
 
     Examples:
 
-        rt comment -t 'Not worth fixing.' -a stddisclaimer.h 23
+        rt comment -m 'Not worth fixing.' -a stddisclaimer.h 23
 
 --
 
 
 --
 
@@ -1732,7 +2349,7 @@ Text:
 
         rt merge <from-id> <to-id>
 
 
         rt merge <from-id> <to-id>
 
-    Merges the two specified tickets.
+    Merges the first ticket specified into the second ticket specified.
 
 --
 
 
 --
 
@@ -1741,16 +2358,16 @@ Text:
 
     Syntax:
 
 
     Syntax:
 
-        rt link [-d] <id-A> <relationship> <id-B>
+        rt link [-d] <id-A> <link> <id-B>
 
     Creates (or, with -d, deletes) a link between the specified tickets.
 
     Creates (or, with -d, deletes) a link between the specified tickets.
-    The relationship can (irrespective of case) be any of:
+    The link 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).
 
 
         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
+    To view a ticket's links, use "rt show ticket/3/links". (See
     "rt help ticket" and "rt help show".)
 
     Options:
     "rt help ticket" and "rt help show".)
 
     Options:
@@ -1778,6 +2395,35 @@ Text:
     
     (XXX: I'm going to have to write it, aren't I?)
 
     
     (XXX: I'm going to have to write it, aren't I?)
 
+    Until it exists here a short description of important constructs:
+
+    The two simple forms of query expressions are the constructs
+    Attribute like Value and
+    Attribute = Value or Attribute != Value
+
+    Whether attributes can be matched using like or using = is built into RT.
+    The attributes id, Queue, Owner Priority and Status require the = or !=
+    tests.
+
+    If Value is a string it must be quoted and may contain the wildcard
+    character %. If the string does not contain white space, the quoting
+    may however be omitted, it will be added automatically when parsing
+    the input.
+
+    Simple query expressions can be combined using and, or and parentheses
+    can be used to group expressions.
+
+    As a special case a standalone string (which would not form a correct
+    query) is transformed into (Owner='string' or Requestor like 'string%')
+    and added to the default query, i.e. the query is narrowed down.
+
+    If no Queue=name clause is contained in the query, a default clause
+    Queue=$config{queue} is added.
+
+    Examples:
+    Status!='resolved' and Status!='rejected'
+    (Owner='myaccount' or Requestor like 'myaccount%') and Status!='resolved'
+
 --
 
 Title: form
 --
 
 Title: form
@@ -1807,7 +2453,11 @@ Text:
 Title: topics
 Text:
 
 Title: topics
 Text:
 
-    Use "rt help <topic>" for help on any of the following subjects:
+    Syntax:
+
+        rt help <topic>
+
+    Get help on any of the following subjects:
 
         - tickets, users, groups, queues.
         - show, edit, ls/list/search, new/create.
 
         - tickets, users, groups, queues.
         - show, edit, ls/list/search, new/create.
@@ -1828,9 +2478,110 @@ Title: example
 Title: examples
 Text:
 
 Title: examples
 Text:
 
-    This section will be filled in with useful examples, once it becomes
-    more clear what examples may be useful.
+    some useful examples
+
+    All the following list requests will be restricted to the default queue.
+    That can be changed by adding the option -q queuename
+
+    List all tickets that are not rejected/resolved
+        rt ls
+    List all tickets that are new and do not have an owner
+        rt ls "status=new and owner=nobody"
+    List all tickets which I have sent or of which I am the owner
+        rt ls myaccount
+    List all attributes for the ticket 6977 (ls -l instead of ls)
+        rt ls -l 6977
+    Show the content of ticket 6977
+        rt show 6977
+    Show all attributes in the ticket and in the history of the ticket
+        rt show -l 6977
+    Comment a ticket (mail is sent to all queue watchers, i.e. AdminCc's)
+        rt comment 6977
+        This will open an editor and lets you add text (attribute Text:)
+        Other attributes may be changed as well, but usually don't do that.
+    Correspond a ticket (like comment, but mail is also sent to requestors)
+        rt correspond 6977
+    Edit a ticket (generic change, interactive using the editor)
+        rt edit 6977
+    Change the owner of a ticket non interactively
+        rt edit 6977 set owner=myaccount
+        or
+        rt give 6977 account
+        or
+        rt take 6977
+    Change the status of a ticket
+        rt edit 6977 set status=resolved
+        or
+        rt resolve 6977
+    Change the status of all tickets I own to resolved !!!
+        rt ls -i owner=myaccount | rt edit - set status=resolved
+
+--
+
+Title: shell
+Text:
+
+    Syntax:
+
+        rt shell
+
+    Opens an interactive shell, at which you can issue commands of 
+    the form "<action> [options] [arguments]".
+
+    To exit the shell, type "quit" or "exit".
+
+    Commands can be given at the shell in the same form as they would 
+    be given at the command line without the leading 'rt' invocation.
+
+    Example:
+        $ rt shell
+        rt> create -t ticket set subject='new' add cc=foo@example.com
+        # Ticket 8 created.
+        rt> quit
+        $
+
+--
+
+Title: take
+Title: untake
+Title: steal
+Text:
+
+    Syntax:
+
+        rt <take|untake|steal> <ticket-id>
 
 
-    For the moment, please consult examples provided with each action.
+    Sets the owner of the specified ticket to the current user, 
+    assuming said user has the bits to do so, or releases the 
+    ticket.  
+    
+    'Take' is used on tickets which are not currently owned 
+    (Owner: Nobody), 'steal' is used on tickets which *are* 
+    currently owned, and 'untake' is used to "release" a ticket 
+    (reset its Owner to Nobody).  'Take' cannot be used on
+    tickets which are currently owned.
+
+    Example:
+        alice$ rt create -t ticket set subject="New ticket"
+        # Ticket 7 created.
+        alice$ rt take 7
+        # Owner changed from Nobody to alice
+        alice$ su bob
+        bob$ rt steal 7
+        # Owner changed from alice to bob
+        bob$ rt untake 7
+        # Owner changed from bob to Nobody
 
 --
 
 --
+
+Title: quit
+Title: exit
+Text:
+
+    Use "quit" or "exit" to leave the shell.  Only valid within shell 
+    mode.
+
+    Example:
+        $ rt shell
+        rt> quit
+        $