| RT/FM The RT Hackers Guide | Not logged in. [Login] |
Graphical Schema Courtesy of XS4ALL.
| Tickets Table
The Tickets table holds, well, Tickets. The API in RT::Ticket covers handling individual Tickets, whilst the API in RT::Tickets covers handling groupings of Tickets. For more information on extra things in an RT::Ticket, see the detailed schema below. There are four main linkages between the Tickets Table and other Tables, being the Transactions, Watchers and Users tables. |
|||
|---|---|---|---|
| Linkage: Tickets.Queue = Queue.id | Linkage: Transaction.Ticket = Ticket.id | Linkage: Watchers.Value = Ticket.id
Where Watchers.Scope = Ticket |
Linkage: Tickets.Owner = User.id | Queue Table
This contains most of the information about various Queues that are configured in your system. |
Transactions Table
This contains meta-information about actions which have been performed on this ticket. This includes incoming Comments or Correspondence, but the actual data is kept in the Attachments table, which is further linked below. |
Watchers Table
This is.. an odd beast, and is only in the 2.0.x series. It contains references to people that are Watchers on a given ticket, such as the original Requestor, additional requestors (etc). In most cases, the Watcher has an 'Owner', which is a reference to a User object. |
Users Table
This contains Users, and and has suitable fields to contain a biography about a person. |
| Linkage: Attachments.TransactionId = Transaction.id | Linkage: Watchers.Owner = Users.id | ||
The methods available from an RT::Queue object that correspond to table fields are as follows:
Table Name: Groups
Columns:
Table Name: ACL
Columns:
Mon, 04 Mar 2002 18:12:12 +0900 I have patched Request Tracker 2.0.11 to work with Japanese, and both email and web work correctly now. Outgoing emails and incoming emails are both supported in Japanese. This patch does not deal with any languages other than Japanese and English, and in fact will probably break if German/French/Russian/whatever messages are sent. This patch requires Jcode.pm from http://openlab.ring.gr.jp/Jcode/ to be installed. It stores all data in the DB in EUC-JP, and converts incoming emails from ISO-2022-JP to EUC-JP. The subject line is correctly MIME decoded/encoded with Base64 then converted, as well. You will want to tell your web server to serve all pages for RT as EUC-JP. In my apache httpd.conf, I used: AddDefaultCharset euc-jp The patch is available at: http://nausicaa.interq.or.jp/rt/rt-japanese.patch Patch follows, against Request Tracker version 2.0.11. diff -ruN rt-2-0-11.orig/lib/RT/Action/SendEmail.pm rt-2-0-11/lib/RT/Action/SendEmail.pm --- rt-2-0-11.orig/lib/RT/Action/SendEmail.pm Wed Nov 7 08:04:17 2001 +++ rt-2-0-11/lib/RT/Action/SendEmail.pm Mon Mar 4 17:25:25 2002 @@ -2,6 +2,8 @@ # Copyright 2000 Jesse Vincentand Tobias Brox # Released under the terms of the GNU Public License +use Jcode; + package RT::Action::SendEmail; require RT::Action::Generic; @@ -98,6 +100,19 @@ $MIMEObj->make_singlepart; + + if (Jcode::getcode($MIMEObj->as_string) ne 'ascii') { + $MIMEObj->head->set('content-type', 'text/plain; charset=iso-2022-jp'); + my $body_r = $MIMEObj->bodyhandle->open("r"); + my $body; + while (defined($_ = $body_r->getline)) { + $body .= Jcode->new($_)->iso_2022_jp; + } + $body_r->close; + my $body_w = $MIMEObj->bodyhandle->open("w"); + $body_w->print($body); + $body_w->close; + } #If we don't have any recipients to send to, don't send a message; @@ -292,6 +307,11 @@ unless ($self->TemplateObj->MIMEObj->head->get('From')) { my $friendly_name=$self->TransactionObj->CreatorObj->RealName; # TODO: this "via RT" should really be site-configurable. + # hack for Japanese - che@debian.org + if (Jcode::getcode($friendly_name) ne 'ascii') { + $friendly_name = Jcode->new($friendly_name)->mime_encode(); + } + $self->SetHeader('From', "$friendly_name via RT <$replyto>"); } @@ -396,7 +416,8 @@ sub SetSubject { my $self = shift; - unless ($self->TemplateObj->MIMEObj->head->get('Subject')) { + my $subj = $self->TemplateObj->MIMEObj->head->get('Subject'); + if (not $subj) { my $message=$self->TransactionObj->Message; my $ticket=$self->TicketObj->Id; @@ -423,11 +444,21 @@ $subject =~ s/(\r\n|\n|\s)/ /gi; + # hack for Japanese + if (Jcode::getcode($subject) ne 'ascii') { + $subject = Jcode->new($subject)->mime_encode(); + } + chomp $subject; $self->SetHeader('Subject',$subject); - - } - return($subject); + return $subject; + } elsif (Jcode::getcode($subj) ne 'ascii') { + my $newsubj = Jcode->new($subject)->mime_encode(); + $self->SetHeader('Subject', $newsubj); + return $newsubj; + } else { + return $subj; + } } # }}} diff -ruN rt-2-0-11.orig/lib/RT/Interface/Email.pm rt-2-0-11/lib/RT/Interface/Email.pm --- rt-2-0-11.orig/lib/RT/Interface/Email.pm Wed Nov 7 08:04:52 2001 +++ rt-2-0-11/lib/RT/Interface/Email.pm Mon Mar 4 17:33:58 2002 @@ -6,6 +6,7 @@ use strict; use Mail::Address; use MIME::Entity; +use Jcode; BEGIN { use Exporter (); @@ -244,8 +245,14 @@ # }}} (temporary directory) #Ok. now that we're set up, let's get the stdin. + + # Hack for Japanese. + my $body = ''; + while (defined(my $line = )) { + $body .= Jcode->new($line)->euc; + } my $entity; - unless ($entity = $parser->read(\*STDIN)) { + unless ($entity = $parser->parse_data($body)) { die "couldn't parse MIME stream"; } #Now we've got a parsed mime object. @@ -254,10 +261,15 @@ my $head = $entity->head; # TODO - information about the charset is lost here! - $head->decode; + #$head->decode; + foreach my $header (qw(Subject To From Cc)) { + if ($head->count($header)) { + $head->replace($header, + Jcode->new($head->get($header))->mime_decode->euc); + } + } return ($entity, $head); - } # }}}
Now heres where we get into the nitty-gritty. A Scrip is the common name given to two dynamically loaded sections of code each time a transaction occurs. Each Queue can have its own collection of Scrips to apply when a transaction occurs on a Ticket within that queue, in addition to the global queue.
Each Scrip has two parts, the ScripCondition and the ScripAction. Each time a transaction occurs, every ScripCondition (of every global and queue-specific Scrip) is executed to see if the Scrip's ScripAction should be executed. It is essential to note that a given Scrip uses a ScripCondition and a ScripAction. ScripConditions and ScripActions are not tied to a specific Scrip.
The RT distribution is supplied with several Scrips which send E-mail base on Templates, but this is certainly not the only use for Scrips.
More detail on how the Scrip system is implemented is given in the
Under the Hood section.
Firstly, copy the perl library file (the foobar.pm file) to the appropriate place in the RT hierarchy. For a ScripCondition, this is the 'Condition' subdirectory of your RT's lib directory. (eg, if your RT libraries are in /usr/local/rt2/lib/RT, then your ScripCondition file should be copied into /usr/local/rt2/lib/RT/Condition/ ). For a ScripAction, this is in the 'Action' subdirectory of your RT's lib directory. (If you've forgotten what you've downloaded, look for the 'package RT::...;' line in the foobar.pm file. If its 'RT::Condition::...;', then its a ScripCondition, it its 'RT::Action::...;', its a ScripAction. If its something else, you're on your own ;))
In both cases, make sure that the user which runs RT's mailgate, and the user which runs RT's Web Interface can access the file.
Secondly, you need to make an entry in the RT database giving details of the ScripCondtion or ScripAction. Most addons have a nifty insert perl script which you can execute. If you don't have one of these, I'd highly advise that you find one, edit it as appropriate, and execute that rather than messing around with hand SQL INSERT statements.
After that, you will be able to refer to your new ScripCondition or ScripActions when in the RT Administration interface.
The end effect is that your tickets can be retrieved by using http://rt.example.com/12345 and your queues can be retrieved by using http://rt.example.com/your-queue-name .
The additions needed to your RT's VirtualHost configuration are:
RewriteEngine On # Treat a single number as a ticket number: RewriteRule ^/([0-9]+)$ /Ticket/Display.html?id=$1 [L,R] # Treat anything else that doesn't look like a file or directory as # a queue name: RewriteRule ^/([^/.]+)$ /Search/Listing.html?ShowQueue=$1 [L,R]
You'll also need to add the following to webrt/Search/Listing.html:
if (my $QueueName = $ARGS{ShowQueue})
{
$QueueName =~ tr/-_/ /;
my $Queue = RT::Queue->new($session{CurrentUser});
if ($Queue->Load($QueueName))
{
%ARGS =
(
ValueOfQueue => $Queue->Id,
ValueOfStatus => 'open',
ValueOfStatus => 'new',
StatusOp => '=',
QueueOp => '=',
NewSearch => '1',
);
}
}
The original posting to rt-users is available here
Michael Brown <mbrown@fensystems.co.uk> has tweaked the above "queue" RewriteRule so that no changes to Search/Listing are required:-
RewriteRule ^([^/.]+)$ /rt/Search/Listing.html?NewSearch=1&TicketsSortBy=Status&TicketsSortOrder=ASC&QueueOp==&ValueOfQueue=$1 [L,R]
Obviously there's a lot of room for fiddling here. Further permutations are left as an exercise for the reader.
This documents how to create a basic ScripCondition yourself. It uses a
comparitive minimum of features, and gives a rough overview of what
features you can use. *Most* of what is described is also applicable to
ScripActions, with the exception that a ScripAction has two invoked
methods, 'Prepare()' and 'Commit()'.
Overview:
For a Scrip to be executed, a number of factors determine RT's choice,
before RT even loads the code behind a Scrip.
Firstly, the Scrip must either be Global, or specific to a Queue.
Secondly, the Transaction being created must be one of those described in
the ScripCondition's ApplicableTransType field.
Thirdly, the ScripCondition's 'IsApplicable()' method must return true.
This is what we're going to cover.
If the ScripCondition's IsApplicable returns true, RT then proceeds to
load the appropriate ScripAction, Prepare() it, and if that was
successful, Commit() it. ( The dry bit that does this is in
lib/RT/Transaction.pm - Create ).
All of the above (except for the public methods() ) are described in the
SQL tables concerning Scrips. Lets have a look there:
SQL references; Scrips;
In the Scrips table, RT looks at the following:
mysql> select * from Scrips;
+----+----------------+-------------+-------------------+-------+----------+---------+---------+---------------+-------------+
| id | ScripCondition | ScripAction | Stage | Queue | Template | Creator | Created | LastUpdatedBy | LastUpdated |
+----+----------------+-------------+-------------------+-------+----------+---------+---------+---------------+-------------+
| 1 | 1 | 1 | TransactionCreate | 2 | 1 | NULL | NULL | NULL | NULL |
[snip]
Firstly, we have the id field of the Scrip. Secondly, we have the ids for
the ScripCondition and ScripAction. These are used as indexes in the
ScripConditions and ScripActions tables.
Next, we have the stage of the Scrip. Currently RT has only one Stage,
being the TransactionCreate, but has the possibility of being extended.
Then we have the Queue identifier (again, used as an index in the Queues
table) and the Template identifier (index into Templates). The remaining
fields are merly informational, and we can ignore them in this context.
SQL References; ScripConditions:
The next step is to look at the ScripCondition. This can be done
selecting the same identifier from the ScripConditions table as is
mentioned in the Condition (see the Scrips table):
mysql> select * from ScripConditions where id = '1';
+----+----------+--------------------------+----------------+----------+----------------------+---------+---------------------+---------------+---------------------+
| id | Name | Description | ExecModule | Argument | ApplicableTransTypes | Creator | Created | LastUpdatedBy | LastUpdated |
+----+----------+--------------------------+----------------+----------+----------------------+---------+---------------------+---------------+---------------------+
| 1 | OnCreate | When a ticket is created | AnyTransaction | NULL | Create | 1 | 2001-12-23 21:25:18 | 1 | 2001-12-23 21:25:18 |
+----+----------+--------------------------+----------------+----------+----------------------+---------+---------------------+---------------+---------------------+
Again, we have the identifier of the ScripCondition. We also have a
descriptive Name, and an expanded Description. Next, we have an odd field
named 'ExecModule', being the real magic behind Scrips, and covered
further on. Next we have an optional Argument, and a list of applicable
Transactions types. The remaining fields are again, informational and not
covered here.
In this case, RT will load this Scrip when a Transaction is Created, hence
the name 'OnCreate' (descriptive only) and 'Create' in
ApplicableTransTypes (used by RT).
Loading a ScripCondition:
Now that we've read the SQL table to know what the ScripCondition is and
we're in an Applicable Transaction Type, the next task is to load it.
This is done via the LoadCondition() method in lib/RT/ScripCondition.pm,
aka:
$self->ExecModule =~ /^(\w+)$/; # match the word
my $module = $1; # store the word
my $type = "RT::Condition::" . $module; # Prepare the word
# Use the word
eval "require $type" || die "Require of $type failed.\n$@\n";
The above retrieves the name of the ExecModule from the database
('AnyTransaction'), and tries to 'require' it as
RT::Condition::AnyTransaction. Replacing the '::'s with '/' indicates
that RT is reading in lib/RT/Condition/AnyTransaction.pm .
( Note that RT is also doing some other tricks to ensure that if RT is
unable to load a ScripCondition, RT doesn't completely die. Having bad
code during testing is ok and won't badly affect your installation. )
Examining a ScripCondition:
Looking at our example Condition (lib/RT/Condition/AnyTransaction.pm), we
can see that its pretty simple.
The essential part of it is the following:
# Declare what we are:
package RT::Condition::AnyTransaction;
# Read in the Generic RT Goodness
require RT::Condition::Generic;
# Load the Generic RT Goodness into our namespace
@ISA = qw(RT::Condition::Generic);
The above lines (note that I've put in extra comments for clarity) do most
of the footwork behind a ScripCondition. We'll cover some of what
RT::Condition::Generic does for you later.
Next in the code is the public method, 'IsApplicable()'. Back in
lib/RT/Transaction.pm, you can see that RT calls the 'IsApplicable()'
method to see if RT should execute the ScripAction. In AnyTransaction,
IsApplicable is extremely simple:
sub IsApplicable {
# Get our OO $self
my $self = shift;
# This routine returns true in any case.
return(1);
}
Writing our Own ScripCondition:
In this example, we're going to create our own ScripCondition. As this is
a step-by-step example, we're not going to do anything complicated, but we
are going to do something useful.
In a typical RT install, a Scrip is supplied that reads something like:
OnCreate AutoreplyToRequestors with template Autoreply
This gives an Autoreply each time that a transaction is created, normally
via Email. Wouldn't it be useful to give a different AutoReply if a
request came in outside Office Hours?
So, we're going to create a ScripCondition that checks what time it is,
and we'll eventually end up with:
OnCreateOfficeHours AutoreplyToRequestors with template Autoreply-hours
OnCreateOutsideHours AutoreplyToRequestors with template Autoreply-outside
Sounds easy, right?
The Right Place to Put ScripConditions:
The correct place to put ScripConditions is in the 'Condition'
subdirectory of your RT's library, ie
'lib/RT/Condition/MyScripCondition.pm'. Personally, I'm a great believer
of maintaining a seperate place for localisations, and I have the
following setup:
/home/rt2/etc/site_scrips/Condition/MyLocalCondition.pm
/home/rt2/lib/RT/Condition/MyLocalCondition.pm --symlink-to-->
/home/rt2/etc/site_scrips/Condition/MyLocalCondition.pm
( This is so that when I upgrade, I just have to recreate the symbolic
links, and not my localisations )
Everyone has their own method, however RT must be able to load
MyLocalCondition.pm from 'lib/RT/Condition/MyLocalCondition.pm'.
Example ScripCondition; Crontab.pm:
Bearing the above in mind, we're going to create our own ScripCondition
named 'Crontab.pm'. Although we're going to appear to end up with 2
ScripConditions, we only need one ExecModule (RT::Condition::Crontab.pm).
Later, we'll be creating two ScripConditions that will have the same
ExecModule, but will differ in their Names, Descriptions and Arguments.
( Code Re-use is Good for you )
Firstly, we set up ourselves:
# ScripCondition: Crontab.pm
# IsApplicable returns true if the current time is within the
# range specified by the Argument, undef otherwise.
# Bruce Campbell says to always give yourself Credit, and
# to use RCS (man rcs):
# $Id$
# Who are we?
package RT::Condition::Crontab;
# Bring all the RT Goodness into our lives.
require RT::Condition::Generic;
# and our namespace
@ISA = qw(RT::Condition::Generic);
# We're going to use this later.
use Set::Crontab;
Now we've got our preparations out of the way, we can do some work.
Remember that the only public method is the 'IsApplicable()' one:
# Override RT::Condition::Generic's IsApplicable method:
sub IsApplicable {
# Get our OO $self.
my $self = shift;
# Prepare our default return value
my $retval = undef;
Now what? We need to get the current time. We'll use RT::Date for this.
my $cur_time = new RT::Date( $RT::Nobody );
$cur_time->SetToNow();
Next, we need to find out the time ranges that we're valid for. For this,
we'll use the Argument that will be stored in the ScripConditions table
later. We'll use the standard crontab specifiers as they're simple, and a
'man crontab' will give the usage.
To allow for multiple time ranges in one ScripCondition, we'll use the ':'
character, so:
my @time_ranges = split( ':', $self->Argument );
Huh? Where do we get the Argument from? Its imported from
RT::Condition::Generic, as a helper method for getting values from
SQL concerning the current ScripCondition, Transaction (etc).
Next, we need to see if 'Now' is within any of the ranges given above.
For this, we need to use the nice Set::Crontab module from CPAN. Do a
"perl -MCPAN -e'install Set::Crontab'" to install this.
# Prepare 'now'.
# We'll use localtime on the Unix() method of RT::Date
my @nowlist = localtime( $cur_time->Unix );
# Build up a compare list.
my @wantlist = ();
# Names are not supported in Set::Crontab
$wantlist[0] = $nowlist[1]; # minute 0-59
$wantlist[1] = $nowlist[2]; # hour 0-23
$wantlist[2] = $nowlist[3]; # day of month 1-31
$wantlist[3] = $nowlist[4] + 1; # month 1-12
$wantlist[4] = $nowlist[6]; # day of week 0-7
We also need to set up the known ranges for cron
# Known ranges
my @known_ranges = (
[0..59], # minute 0-59
[0..23], # hour 0-23
[1..31], # day of month 1-31
[1..12], # month 1-12
[0..7], # day of week 0-7
);
We loop through the various @time_ranges that we've found.
foreach my $this_range ( @time_ranges ){
# Remove leading or trailing space (we split() on that later)
$this_range =~ s/^\s*//g;
$this_range =~ s/\s*$//g;
# How many of the fields have we matched so far?
my $matched = 0;
# Split apart the fields based on whitespace.
my @this_list = split( /\s+/, $this_range );
And we also loop through each field in the time range.
# See if each field is in within range.
my $loop = 0;
while( ( $loop < scalar @wantlist ) &&
( $loop < scalar @this_list ) ){
my $tst = Set::Crontab->new(
$this_list[$loop],
$known_ranges[$loop] );
if( $tst->contains(
$wantlist[$loop] ){
$matched++;
}
Always remember to keep moving forward.
# Increment the loop
$loop++;
}
Now we check to see whether we matched them all. If we didn't, its
obviously not this time range.
# Did we match them all?
if( ( $matched == $loop ) &&
( $loop == scalar @wantlist ) ){
# We did. Set our return value.
$retval++;
}
}
Finally, we return with our results.
# retval is undef when declared, and only incremented
# if a time value matches.
return( $retval );
}
The very last thing we need in Crontab.pm is to return true for the
'require'. So, we put:
1;
At this point, it might be best to grab the working version from:
http://www.fsck.com/pub/contrib/2.0/
( Note, I'm not sure where Jesse is going to put it )
Inserting a ScripCondition into the Database; Template:
Now that we have our Code to execute the ScripCondition, we need to Create
the Condition in the SQL database. With RT2.0.x, there is an interface
for Creating Scrips, but no easy method of calling it from the Web or
command line interfaces.
Instead, we're going to write a perl program to do it for us. Save this
as 'insert_condition_Crontab_Template.pl':
#!/usr/bin/perl -w
Remember to change these two variables as needed.
# Where is our RT library?
use lib "!!CHANGE_ME_TO_RT_LIB_DIRECTORY!!";
# Where is our configuration?
use lib "!!CHANGE_ME_TO_RT_ETC_DIRECTORY!!";
# Get our base configuration (config.pm)
use config;
# pretty errors.
use Carp;
# use a few more things related to RT
use RT::Handle;
use RT::User;
use RT::CurrentUser;
# Connect to the Database (low level)
$RT::Handle = new RT::Handle( $RT::DatabaseType );
$RT::Handle->Connect;
#Put together a current user object so we can create a User object
my $CurrentUser = new RT::CurrentUser();
#now that we bootstrapped that little bit, we can use the standard RT cli
# helpers to do what we need
use RT::Interface::CLI qw(CleanEnv LoadConfig DBConnect
GetCurrentUser GetMessageContent);
#Clean out all the nasties from the environment
CleanEnv();
#Load etc/config.pm and drop privs
LoadConfig();
#Connect to the database and get RT::SystemUser and RT::Nobody loaded
DBConnect();
# Load the System User
$CurrentUser->LoadByName('RT_System');
At this point, RT has connected to the database as the System User. From
here, we need to define what we put into the database:
# The Argument for Crontab.pm is a ':' seperated list of
# crontab-like entries. See 'man crontab' (5) for a full
# description of crontab configuration.
my @ScripConditions = (
{
Name => 'OnCreateHours',
Description => 'Matches at any time',
ApplicableTransTypes => 'Create',
Argument => '* * * * *',
ExecModule => 'Crontab',
},
);
And finally, we need to put it into the database:
# The above is the user changable stuff. Now create a Scrip with
# the supplied data
print "Creating ScripConditions...";
use RT::ScripCondition;
for $item (@ScripConditions) {
my $new_entry = new RT::ScripCondition($CurrentUser);
my $return = $new_entry->Create(%$item);
print $return.".";
}
print "done.\n";
$RT::Handle->Disconnect();
1;
Save that file as 'insert_condition_Crontab_Template.pl' (I like to keep it
these in /home/rt2/etc/site_scrips/notes/ myself, but you may have
different ideas of course), and proceed to the next step.
This should also be available from the same directory on www.fsck.com that
you found Crontab.pm.
Inserting a ScripCondition into the Database; Specifics:
Now that we have a Template for creating a ScripCondition, we're going to
use this to create two ScripConditions, 'OnCreateOfficeHours' and
'OnCreateOutsideHours' as suggested earlier.
To do this, make two copies of the Template as
'insert_condition_OnCreateOfficeHours.pl' and
'insert_condition_OnCreateOutsideHours.pl'. Note that the names of the
files is arbitary, but simply helps to remember what you're dealing with.
Within the OfficeHours one, replace the Name, Description and Argument as
follows:
Name => 'OnCreateOfficeHours',
Description => 'Fires when a Create is done within Office \
Hours',
Argument => '* 9-16 * * 1-5',
Change as appropriate if your office hours are outside the sterotypical
9-5. For example, the following describes 8:45am to 17:34pm:
Argument => '45-59 8 * * 1-5:* 9-16 * * 1-5:0-15 17 * * 1-5',
Within the OutsideHours one, replace the Name, Description and Argument as
follows:
Name => 'OnCreateOutsideHours',
Description => 'Fires when a Create is done outside Office \
Hours',
Argument => '* 0-8 * * 1-5:* 17-23 * * 1-5:* * * * 0,6',
All that you need to do now is to run these perl programs (but not the
Template one), and you've created two new ScripConditions.
Using your New ScripConditions:
Using these ScripConditions is as easy as using any normal ScripCondition.
Firstly, you need to create two new Templates, 'Autoreply-hours' and
'Autoreply-outside' with appropriate texts, ie:
Autoreply-hours:
Greetings,
This message has been automatically generated in response
to the creation of a trouble ticket regarding:
"{$Ticket->Subject()}" .
We are on duty now, so you should receive a reply within
the next hour.
(etc)
Autoreply-outside
Greetings,
This message has been automatically generated in response
to the creation of a trouble ticket regarding:
"{$Ticket->Subject()}" .
Our office hours are between 9am and 5pm weekdays, and
our local timezone is CET (GMT+0100). We will answer your
query on the next working day.
(etc)
Finally, you need to create the Scrips using the Templates and the
Conditions that we've written, as:
OnCreateOfficeHours AutoreplyToRequestors with template Autoreply-hours
OnCreateOutsideHours AutoreplyToRequestors with template Autoreply-outside
Thats all there is to it. Really.
Other things from RT::Condition::Generic:
The Generic module for ScripConditions handles all of the setup for that you
need in order to create your own ScripCondition. By 'require'ing it, you
can just override what you need, normally just the 'IsApplicable' method.
The useful methods in Generic are the Argument() which we've seen above,
TicketObj() which gives you access to the current Ticket object, and
TransactionObj() which gives you access to the current Transaction object.
Both of these can be referenced from within
RT::Condition::MyLocalCondition as $self->TicketObj or
$self->TransactionObj. Because they are references to Ticket and
Transaction objects, you can do neat things like:
if( $self->TransactionObj->CreatorObj->EmailAddress() =~ /magic/ ){
At this point, the best method to learn is to use some examples. Plenty
of examples can be found on the Rt distribution site, at
http://www.fsck.com/pub/rt/contrib/2.0 . As a guideline for understanding
the code, always remember that '$self' refers to something within
ourselves (man perlboot).
If you see a reference that you don't get, search through the other files
that have been 'require'd or 'use'd until you find it. Sometimes you will
have to go back into the code to find out where something is defined.
For example, RT::Condition::Generic uses $self->{'TransactionObj'}. This
gets loaded by RT::ScripCondition->LoadCondition(), which is handed the
Transaction Object when called. The only place LoadCondition() is called
is within RT::Transaction->Create(), which passes a copy of itself.
phew. (theres no easy way of describing it. Use grep over the whole
lib/RT tree if you have to)
[ This was originally posted to rt-users ] [ TODO - break into component parts ] From roth@XXXX.XXX Wed Apr 24 11:39:12 2002 Date: Thu, 18 Apr 2002 18:00:33 -0500 From: Mark D. RothTo: rt-users@lists.fsck.com Subject: [rt-users] RFC: RT install w/o setgid Perl or mod_perl I've only recently discovered RT and joined the list, so I apologize in advance if any of this is an FAQ. I didn't see anything similar in the list archives, and I thought people might be interested in how I have RT (2.0.13) set up. I'd also like to hear any suggestions that anyone may have on how to improve my configuration. Background Information ---------------------- My group's primary function is to run systems that host central services such as mail, LDAP, news, web servers, etc. We don't run the applications that run on our systems; we just provide the underlying platform on which our customers install and manage their applications. As a result of this division of responsibilities, we've developed a lot of infrastructure to allow our customers to control as much of their software and data as possible, even though they don't have root access. In particular, we create a unique pseudo-user for each application, and we try to install everything related to the application in the pseudo-user's home directory. The customer then su's to the pseudo-user to manage the service. For example, for our LDAP service, we created a pseudo-user named "ldap" with home directory "/services/ldap". All of the LDAP server software, directory data, and support scripts are owned by this user and installed in its home directory. For web-based services, we've established this convention for URL-to-directory mappings: DIRECTORY URL ---------------------------------------------------------------- ~pseudouser/public_html/http http://hostname/pseudouser/ ~pseudouser/public_html/https https://hostname/pseudouser/ Rather than just using an Apache "Alias" directive to establish this mapping, we use mod_rewrite to rewrite the requested URL internally using ~pseudouser notation. This allows us to use Apache's suEXEC mechanism to invoke CGI scripts as the appropriate pseudo-user, rather than as user httpd. Goals for RT Installation ------------------------- Given that we already have infrastructure in place for running applications as pseudo-users, I wanted to use the same model for my RT installation. In particular, I had two main goals: 1. Install RT under a single pseudo-user account and run every part of it as that user. (I created a pseudo-user named "rt" with a home directory of "/services/rt" for this purpose.) 2. Use my site's existing perl and apache packages instead of creating special builds just for RT. (This means no set[ug]id perl scripts and no mod_perl.) These goals presented several interesting challenges. Installation Process -------------------- Because the RT installation procedure assumes that you'll be running different parts as different users, it includes commands that chown various files, so it must be run as root. After closely scrutinizing what it was going to do, I ran it as root, but quickly chown'ed all of the installed files to user rt. I also removed all of the set[ug]id bits. RT Mail Gateway --------------- Since rt-mailgate could not be a setgid script, I needed to find a way to run it as user "rt". The RT docs say to run it directly from sendmail's aliases file, as follows: sysmgr: "|/services/rt/rt2/bin/rt-mailgate --queue sysmgr --action correspond" That would result in rt-mailgate being run as user daemon. To get around this, I changed the alias to point to the rt user: sysmgr: rt Then, as user rt, I use procmail to run rt-mailgate with the appropriate options depending on what address the message was sent to. Thus, rt-mailgate gets run as user rt. RT Web Interface ---------------- The next problem was getting the web interface to run as user rt instead of as user httpd. In this case, even though I didn't want to use mod_perl in the first place, the fact that I couldn't worked out in my favor, since FastCGI can be configured to use Apache's suEXEC mechanism to run external programs as alternate users. The first problem I ran into in setting up FastCGI was a mod_perl dependency problem in mason_handler.fcgi. Commenting out this line seemed to fix it: use HTML::Mason::ApacheHandler; (I hope commenting this out doesn't have any bad side-effects, but I haven't noticed any problems so far. Is this a bug, or is there something weird about my setup?) Next, in order to be processed by our mod_rewrite rules and suEXEC, the FastCGI handler had to be installed in ~rt/public_html/https. However, since I had installed RT in ~rt/rt2, I solved this problem with a hard link. (The down-side of this is that I'll probably need to recreate the hard link when I upgrade RT.) Finally, I needed to create a symlink ~rt/public_html/https/images -> ~rt/rt2/WebRT/html/images so that the images would be accessible. Here are the relevant snippets from httpd.conf: -------------------------------------------------------------------------- LoadModule fastcgi_module /usr/local/libexec/mod_fastcgi.so AddModule mod_fastcgi.c AddHandler fastcgi-script .fcgi FastCgiWrapper /usr/local/sbin/suexec FastCgiIpcDir /var/lib/httpd RewriteEngine on ### Allow the translated subrequest to pass straight through. RewriteCond %{IS_SUBREQ} =true RewriteRule ^/~rt/https/ - [L,PT] ### In case the rewritten URI leaks out to the browser, ### redirect it back to the advertised location. RewriteRule ^/~(rt)(/https)?(/(.*))?$ /$1/$4 [R=permanent,L] ### Redirect to add trailing '/' for directory names. RewriteRule ^/(rt)$ /$1/ [R=permanent,L] ### Handle requests for the advertised URIs by translating them ### and passing them through in a subrequest. RewriteRule ^/rt/images(/(.*))?$ /~rt/https/images/$2 [L,PT] RewriteRule ^/rt/(.*)$ /~rt/https/mason_handler.fcgi/$1 [L,PT] -------------------------------------------------------------------------- Running the RT web interface as user rt eliminated the need for the WebRT/data and WebRT/sessiondata directories to be owned by user httpd. Conclusion and Suggestions -------------------------- There were a lot of issues that needed to be identified and addressed in order to install RT under its own pseudo-user. However, once this was done, the procedure was actually fairly easy. Based on this experience, here are a few off-the-cuff suggestions, for whatever they're worth: * Change the installation procedure to use autoconf. This would make it easier to install different parts of RT in different locations and with different options. It would also make it easier to build distributable packages of RT. * Change the installation procedure not to assume that different files and directories need to have different ownerships. This would allow RT to be installed by a user other than root. * Improve the documentation. In particular, the installation instructions need to be more precise and more detailed. Well, I think that's it for now... Please let me know if you have any questions, comments, or suggestions. Thanks! -- Mark D. Rothhttp://www.feep.net/~roth/ _______________________________________________ rt-users mailing list rt-users@lists.fsck.com http://lists.fsck.com/mailman/listinfo/rt-users
#!/usr/bin/perl
# This will run a command, pipe stuff into it, collect the output, and NOT BLOCK whilst doing so. Also has a timeout.
# Stuff for open2
use IPC::Open2;
# Stuff for waitpid
use POSIX ":sys_wait_h";
# Stuff for setting NONBLOCK on handles.
use Fcntl qw(F_GETFL F_SETFL O_NONBLOCK);
# Dummy commands.
my @Commands = ( "sort", "-u" );
# Open the command with an input and output handle. Save the process
# id of this.
$pid = open2(\*Reader, \*Writer, join( ' ', @Commands ) ) || die "boing";
my @foo = ();
# Dummy text to spit out.
open( INPUT, "dummy.input.file" );
my $input = "";
while( my $line = ){
chomp( $line );
# push @foo, $line;
$input .= $line . "\n";
}
close( INPUT );
my $timeout = 5;
# Get the flags.
my $flags = fcntl(Writer, F_GETFL, 0)
or die "Can't get flags for the socket: $!\n";
# set NONBLOCK on the Writer.
$flags = fcntl(Writer, F_SETFL, $flags | O_NONBLOCK)
or die "Can't set flags on the socket: $!\n";
$flags = fcntl(Reader, F_GETFL, 0)
or die "Can't get flags for the socket: $!\n";
# set NONBLOCK on the Reader.
$flags = fcntl(Reader, F_SETFL, $flags | O_NONBLOCK)
or die "Can't set flags on the socket: $!\n";
my $bytes_written = 0;
my $bytes_wrote = 0;
my $max_bytes = length( $input );
# Use 80 or the length.
my $chunk_size = ( 1024 < $max_bytes ) ? 1024 : $max_bytes;
my $output = "";
my $stillgoing = $timeout;
my $last_success = time;
while( $stillgoing ){
# Attempt to write to the chunk size using syswrite
my $these_bytes = undef;
if( $bytes_written < $max_bytes ){
my $this_chunk = ( ( $max_bytes - $bytes_written ) > $chunk_size ) ? $chunk_size : ( $max_bytes - $bytes_written );
print "Using $this_chunk\n";
$these_bytes = syswrite( Writer, $input, $this_chunk, $bytes_written );
}
if( defined( $these_bytes ) ){
$bytes_written += $these_bytes;
print "Wrote $these_bytes - $bytes_written\n";
$last_success = time;
if( $bytes_written >= $max_bytes ){
close( Writer );
}
}else{
print "Could not write? - $bytes_written\n";
}
# Attempt to read from the input.
my $this_output = "";
$these_bytes = sysread( Reader, $this_output, $chunk_size );
if( defined( $these_bytes ) ){
$bytes_read += $these_bytes;
print "Read $these_bytes - $bytes_read\n";
if( $these_bytes == 0 ){
# end of file?
$stillgoing--;
}
$last_success = time;
}else{
print "Could not read? - $bytes_read\n";
# if( eof( Reader ) && eof( Writer ) ){
# $stillgoing--;
# }
}
unless( time < ( $last_success + $timeout ) ){
# Timeout. Send a TERM signal
print STDERR "Timeout ($timeout, $stillgoing)\n";
if( waitpid( $pid, WNOHANG ) == 0 ){
print STDERR "Timeout and process running, sending TERM\n";
kill( SIGTERM, $pid );
}
print STDERR "Timeout2 ($timeout, $stillgoing)\n";
$stillgoing--;
}
}
close( Writer );
close( Reader );
print "Finished?\n";
# Reap the child.
if( waitpid( $pid, WNOHANG ) == 0 ){
# process is still running. die.
print STDERR "Process still running, sending kill\n";
kill( SIGKILL, $pid );
}
This snippet below is what I use on my systems to run RT without relying on suid bits. The essential magic is to use procmail's magic based on the ownership of a file in /etc/procmailrcs/ (see procmail man page) to become the RT user.
aliases file:
# Run rt2 as the rt2 user, not as root.
test-rt2: "| /usr/bin/procmail -m /etc/procmailrcs/rt2-test"
/etc/procmailrcs/rt2-test:
# This file MUST be owned by the rt2 user. See '-m' description
# in the procmail man page.
HOME=/home/rt2
INCLUDERC=$HOME/.procmailrc-test
# are we still here after including? Dump it to someone
:0
! joe_bloggs@example.com
( The above two snippets gets procmail running, as the rt2 user, a
procmail file for the specific queue. I really really dislike relying
on setuid stuff. )
/home/rt2/.procmailrc-test
# This file does any filtering/mail manipulations/etc before
# calling rt-mailgate.
HOME=/home/rt2
LOGFILE=log/procmail.log
VERBOSE=no
# Go away you naughty annoying spammer
:0
* ^From:.*badguy@example.com
/dev/null
# Place mailfiltering here
:0 f
| /home/rt2/bin/my_custom_filter_program
# Accept the mail into mailgate. We { } enclose it incase we want
# a last-ditch filter.
:0
{
# Use a lockfile to ensure only one rt-mailgate process.
:0 :.procmail.lock
| /home/rt2/bin/rt-mailgate --queue TEST --action correspond
# Something went wrong. Dump it to someone
:0e
! joe.bloggs@example.com
}
Firstly, decide whether you need to reinvent the wheel. Many things have been previously decide, some have been implemented and others have been dropped as a bad idea. Research is always important. If you have a good idea, chances are that you're not the only one, and it might even be mentioned within RT/FM (that 'Search' tab up the top is what you're looking for).
Secondly, keep track of your work. If you are fiddling with the core of RT, then please take the time to use a good Version Control System so you can understand what you have done, and can back out of any changes. Common, but still going strong, are 'rcs' and 'cvs'. Newer ones are 'ageis', 'bitkeeper' and a host of others.
Thirdly, make sure that you can provide a reference point. For changes to RT's core, this means a context 'diff' against a particular RT version, the later the better. Always remember to mention which version of RT you are using as a baseline.
Fourthly, you can always ask for help.
Fifthly, try to do as much testing as possible. If you cannot test your changes (huh?), make sure that you explicitly say 'This is untested code and may not work'. Someone else may fix it for you if they also see a need.
Sixthly, tell Jesse about it. Preferably, this is in the form of a complete description of what your change should do, what else is needed, your base version, and of course the caveats and security risks inherent in your change. Remember to use RT's own tracking system for this (using, curiously enough, RT), or a posting to the rt-devel@lists.fsck.com mailing list.