mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-08 04:27:37 +00:00
dc8468bb9b
1. BugzillaEmail.pm is a (kinda, sorta) module which is going to hold the various code which is common between all the scripts which access bugzilla via email. 2. bug_email.pl uses this module 3. bugzilla_email_append.pl is a mostly ready script which will append messages to existing bugs.
1284 lines
36 KiB
Perl
Executable File
1284 lines
36 KiB
Perl
Executable File
#!/usr/bin/perl -w
|
|
# -*- Mode: perl; indent-tabs-mode: nil -*-
|
|
#
|
|
# The contents of this file are subject to the Mozilla Public
|
|
# License Version 1.1 (the "License"); you may not use this file
|
|
# except in compliance with the License. You may obtain a copy of
|
|
# the License at http://www.mozilla.org/MPL/
|
|
#
|
|
# Software distributed under the License is distributed on an "AS
|
|
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
|
# implied. See the License for the specific language governing
|
|
# rights and limitations under the License.
|
|
#
|
|
# The Original Code is the Bugzilla Bug Tracking System.
|
|
#
|
|
# The Initial Developer of the Original Code is Netscape Communications
|
|
# Corporation. Portions created by Netscape are
|
|
# Copyright (C) 1998 Netscape Communications Corporation. All
|
|
# Rights Reserved.
|
|
# Contributor(s): Terry Weissman <terry@mozilla.org>
|
|
# Gregor Fischer <fischer@suse.de>
|
|
# Klaas Freitag <freitag@suse.de>
|
|
# Seth Landsman <seth@dworkin.net>
|
|
###############################################################
|
|
# Bugzilla: Create a new bug via email
|
|
###############################################################
|
|
# The email needs to be feeded to this program on STDIN.
|
|
# This is usually done by having an entry like this in your
|
|
# .procmailrc:
|
|
#
|
|
# BUGZILLA_HOME=/usr/local/httpd/htdocs/bugzilla
|
|
# :0 c
|
|
# |(cd $BUGZILLA_HOME/contrib; ./bug_email.pl)
|
|
#
|
|
#
|
|
# Installation note:
|
|
#
|
|
# You need to work with bug_email.pl the MIME::Parser installed.
|
|
#
|
|
# $Id: bug_email.pl,v 1.5 2000/03/15 22:29:45 seth%cs.brandeis.edu Exp $
|
|
###############################################################
|
|
|
|
# 02/12/2000 (SML)
|
|
# - updates to work with most recent database changes to the bugs database
|
|
# - updated so that it works out of bugzilla/contrib
|
|
# - initial checkin into the mozilla CVS tree (yay)
|
|
|
|
# 02/13/2000 (SML)
|
|
# - email transformation code.
|
|
# EMAIL_TRANSFORM_NONE does exact email matches
|
|
# EMAIL_TRANSFORM_NAME_ONLY matches on the username
|
|
# EMAIL_TRANSFORM_BASE_DOMAIN matches on the username and checks the domain of
|
|
# to see that the one in the database is a subset of the one in the sender address
|
|
# this is probably prone to false positives and probably needs more work.
|
|
|
|
# 03/07/2000 (SML)
|
|
# - added in $DEFAULT_PRODUCT and $DEFAULT_COMPONENT. i.e., if $DEFAULT_PRODUCT = "PENDING",
|
|
# any email submitted bug will be entered with a product of PENDING, if no other product is
|
|
# specified in the email.
|
|
|
|
# Next round of revisions :
|
|
# - querying a bug over email
|
|
# - appending a bug over email
|
|
# - keywords over email
|
|
# - use the globals.pl parameters functionality to edit and save this script's parameters
|
|
# - integrate some setup in the checksetup.pl script
|
|
# - gpg signatures for security
|
|
|
|
use diagnostics;
|
|
use strict;
|
|
use MIME::Parser;
|
|
|
|
push @INC, "../."; # this script now lives in contrib
|
|
|
|
require "globals.pl";
|
|
require "BugzillaEmail.pm";
|
|
|
|
my @mailerrors = (); # Buffer for Errors in the mail
|
|
my @mailwarnings = (); # Buffer for Warnings found in the mail
|
|
my $critical_err = 0; # Counter for critical errors - must be zero for success
|
|
my %Control;
|
|
my $Header = "";
|
|
my @RequiredLabels = ();
|
|
my @AllowedLabels = ();
|
|
my $Body = "";
|
|
my @attachments = ();
|
|
|
|
my $product_valid = 0;
|
|
my $test = 0;
|
|
my $restricted = 0;
|
|
my $SenderShort;
|
|
my $Message_ID;
|
|
|
|
# change to use default product / component functionality
|
|
my $DEFAULT_PRODUCT = "PENDING";
|
|
my $DEFAULT_COMPONENT = "PENDING";
|
|
|
|
###############################################################
|
|
# storeAttachments
|
|
#
|
|
# in this sub, attachments found in the dump-sub will be written to
|
|
# the database. The info, which attachments need saving is stored
|
|
# in the global @attachments-list.
|
|
# The sub returns the number of stored attachments.
|
|
sub storeAttachments( $$ )
|
|
{
|
|
my ($bugid, $submitter_id ) = @_;
|
|
my $maxsize = 0;
|
|
my $data;
|
|
my $listref = \@attachments;
|
|
my $att_count = 0;
|
|
|
|
$submitter_id ||= 0;
|
|
|
|
foreach my $pairref ( @$listref ) {
|
|
my ($decoded_file, $mime, $on_disk, $description) = @$pairref;
|
|
|
|
|
|
# Size check - mysql has a maximum space for the data ?
|
|
$maxsize = 1047552; # should be queried by a system( "mysqld --help" );,
|
|
# but this seems not to be supported by all current mysql-versions
|
|
|
|
# Read data file binary
|
|
if( $on_disk ) {
|
|
if( open( FILE, "$decoded_file" )) {
|
|
binmode FILE;
|
|
read FILE, $data, $maxsize;
|
|
close FILE;
|
|
$att_count ++;
|
|
} else {
|
|
print "Error while reading attachment $decoded_file!\n";
|
|
next;
|
|
}
|
|
# print "unlinking data/mimedump-tmp/$decoded_file";
|
|
# unlink "data/mimedump-tmp/$decoded_file";
|
|
} else {
|
|
# data is in the scalar
|
|
$data = $decoded_file;
|
|
}
|
|
|
|
|
|
# Make SQL-String
|
|
my $sql = "insert into attachments (bug_id, creation_ts, description, mimetype, ispatch, filename, thedata, submitter_id) values (";
|
|
$sql .= "$bugid, now(), " . SqlQuote( $description ) . ", ";
|
|
$sql .= SqlQuote( $mime ) . ", ";
|
|
$sql .= "0, ";
|
|
$sql .= SqlQuote( $decoded_file ) . ", ";
|
|
$sql .= SqlQuote( $data ) . ", ";
|
|
$sql .= "$submitter_id );";
|
|
SendSQL( $sql ) unless( $test );
|
|
}
|
|
|
|
return( $att_count );
|
|
}
|
|
|
|
|
|
|
|
###############################################################
|
|
# Beautification
|
|
sub horLine( )
|
|
{
|
|
return( "-----------------------------------------------------------------------\n" );
|
|
}
|
|
|
|
|
|
###############################################################
|
|
# Check if $Name is in $GroupName
|
|
|
|
# This is no more CreateBugs group, so I'm using this routine to just determine if the user is
|
|
# in the database. Eventually, here should be a seperate routine or renamed, or something (SML)
|
|
sub CheckPermissions {
|
|
my ($GroupName, $Name) = @_;
|
|
|
|
# SendSQL("select login_name from profiles,groups where groups.name='$GroupName' and profiles.groupset & groups.bit = groups.bit and profiles.login_name=\'$Name\'");
|
|
# my $NewName = FetchOneColumn();
|
|
# if ( $NewName eq $Name ) {
|
|
# return $Name;
|
|
# } else {
|
|
# return;
|
|
# }
|
|
# my $query = "SELECT login_name FROM profiles WHERE profiles.login_name=\'$Name\'";
|
|
# SendSQL($query);
|
|
# my $check_name = FetchOneColumn();
|
|
# if ($check_name eq $Name) {
|
|
# return $Name;
|
|
# } else {
|
|
# return;
|
|
# }
|
|
return findUser($Name);
|
|
}
|
|
|
|
###############################################################
|
|
# Check if product is valid.
|
|
sub CheckProduct {
|
|
my $Product = shift;
|
|
|
|
SendSQL("select product from products where product='$Product'");
|
|
my $Result = FetchOneColumn();
|
|
if (lc($Result) eq lc($Product)) {
|
|
return $Result;
|
|
} else {
|
|
return "";
|
|
}
|
|
}
|
|
|
|
###############################################################
|
|
# Check if component is valid for product.
|
|
sub CheckComponent {
|
|
my $Product = shift;
|
|
my $Component = shift;
|
|
|
|
SendSQL("select value from components where program=" . SqlQuote($Product) . " and value=" . SqlQuote($Component) . "");
|
|
my $Result = FetchOneColumn();
|
|
if (lc($Result) eq lc($Component)) {
|
|
return $Result;
|
|
} else {
|
|
return "";
|
|
}
|
|
}
|
|
|
|
###############################################################
|
|
# Check if component is valid for product.
|
|
sub CheckVersion {
|
|
my $Product = shift;
|
|
my $Version = shift;
|
|
|
|
SendSQL("select value from versions where program=" . SqlQuote($Product) . " and value=" . SqlQuote($Version) . "");
|
|
my $Result = FetchOneColumn();
|
|
if (lc($Result) eq lc($Version)) {
|
|
return $Result;
|
|
} else {
|
|
return "";
|
|
}
|
|
}
|
|
|
|
###############################################################
|
|
# Reply to a mail.
|
|
sub Reply( $$$$ ) {
|
|
my ($Sender, $MessageID, $Subject, $Text) = @_;
|
|
|
|
|
|
die "Cannot find sender-email-address" unless defined( $Sender );
|
|
|
|
if( $test ) {
|
|
open( MAIL, ">>data/bug_email_test.log" );
|
|
}
|
|
else {
|
|
open( MAIL, "| /usr/sbin/sendmail -t" );
|
|
}
|
|
|
|
print MAIL "To: $Sender\n";
|
|
print MAIL "From: Bugzilla Mailinterface<yourmail\@here.com>\n";
|
|
print MAIL "Subject: $Subject\n";
|
|
print MAIL "In-Reply-To: $MessageID\n" if ( defined( $MessageID ));
|
|
print MAIL "\n";
|
|
print MAIL "$Text";
|
|
close( MAIL );
|
|
|
|
}
|
|
|
|
|
|
###############################################################
|
|
# getEnumList
|
|
# Queries the Database for the table description and figures the
|
|
# enum-settings out - usefull for checking fields for enums like
|
|
# prios
|
|
sub getEnumList( $ )
|
|
{
|
|
my ($fieldname) = @_;
|
|
SendSQL( "describe bugs $fieldname" );
|
|
my ($f, $type) = FetchSQLData();
|
|
|
|
# delete unneeded stuff
|
|
$type =~ s/enum\(|\)//g;
|
|
$type =~ s/\',//g;
|
|
|
|
my @all_prios = split( /\'/, $type );
|
|
return( @all_prios );
|
|
}
|
|
|
|
###############################################################
|
|
# CheckPriority
|
|
# Checks, if the priority setting is one of the enums defined
|
|
# in the data base
|
|
# Uses the global var. $Control{ 'priority' }
|
|
sub CheckPriority
|
|
{
|
|
my $prio = ($Control{'priority'} ||= "");
|
|
my @all_prios = getEnumList( "priority" );
|
|
|
|
if( (lsearch( \@all_prios, $prio ) == -1) || $prio eq "" ) {
|
|
# OK, Prio was not defined - create Answer
|
|
my $Text = "You sent wrong priority-setting, valid values are:" .
|
|
join( "\n\t", @all_prios ) . "\n\n";
|
|
$Text .= "* The priority is set to the default value ".
|
|
SqlQuote( Param('defaultpriority')) . "\n";
|
|
|
|
BugMailError( 0, $Text );
|
|
|
|
# set default value from param-file
|
|
$Control{'priority'} = Param( 'defaultpriority' );
|
|
} else {
|
|
# Nothing to do
|
|
}
|
|
}
|
|
|
|
###############################################################
|
|
# CheckSeverity
|
|
# checks the bug_severity
|
|
sub CheckSeverity
|
|
{
|
|
my $sever = ($Control{'bug_severity'} ||= "" );
|
|
my @all_sever = getEnumList( "bug_severity" );
|
|
|
|
if( (lsearch( \@all_sever, $sever ) == -1) || $sever eq "" ) {
|
|
# OK, Prio was not defined - create Answer
|
|
my $Text = "You sent wrong bug_severity-setting, valid values are:" .
|
|
join( "\n\t", @all_sever ) . "\n\n";
|
|
$Text .= "* The bug_severity is set to the default value ".
|
|
SqlQuote( "normal" ) . "\n";
|
|
|
|
BugMailError( 0, $Text );
|
|
|
|
# set default value from param-file
|
|
$Control{'bug_severity'} = "normal";
|
|
}
|
|
}
|
|
|
|
###############################################################
|
|
# CheckArea
|
|
# checks the area-field
|
|
sub CheckArea
|
|
{
|
|
my $area = ($Control{'area'} ||= "" );
|
|
my @all= getEnumList( "area" );
|
|
|
|
if( (lsearch( \@all, $area ) == -1) || $area eq "" ) {
|
|
# OK, Area was not defined - create Answer
|
|
my $Text = "You sent wrong area-setting, valid values are:" .
|
|
join( "\n\t", @all ) . "\n\n";
|
|
$Text .= "* The area is set to the default value ".
|
|
SqlQuote( "BUILD" ) . "\n";
|
|
|
|
BugMailError( 0, $Text );
|
|
|
|
# set default value from param-file
|
|
$Control{'area'} = "BUILD";
|
|
}
|
|
}
|
|
|
|
###############################################################
|
|
# CheckPlatform
|
|
# checks the given Platform and corrects it
|
|
sub CheckPlatform
|
|
{
|
|
my $platform = ($Control{'rep_platform'} ||= "" );
|
|
my @all = getEnumList( "rep_platform" );
|
|
|
|
if( (lsearch( \@all, $platform ) == -1) || $platform eq "" ) {
|
|
# OK, Prio was not defined - create Answer
|
|
my $Text = "You sent wrong platform-setting, valid values are:" .
|
|
join( "\n\t", @all ) . "\n\n";
|
|
$Text .= "* The rep_platform is set to the default value ".
|
|
SqlQuote( "All" ) . "\n";
|
|
|
|
BugMailError( 0, $Text );
|
|
|
|
# set default value from param-file
|
|
$Control{'rep_platform'} = "All";
|
|
}
|
|
}
|
|
|
|
###############################################################
|
|
# CheckSystem
|
|
# checks the given Op-Sys and corrects it
|
|
sub CheckSystem
|
|
{
|
|
my $sys = ($Control{'op_sys'} ||= "" );
|
|
my @all = getEnumList( "op_sys" );
|
|
|
|
if( (lsearch( \@all, $sys ) == -1) || $sys eq "" ) {
|
|
# OK, Prio was not defined - create Answer
|
|
my $Text = "You sent wrong OS-setting, valid values are:" .
|
|
join( "\n\t", @all ) . "\n\n";
|
|
$Text .= "* The op_sys is set to the default value ".
|
|
SqlQuote( "Linux" ) . "\n";
|
|
|
|
BugMailError( 0, $Text );
|
|
|
|
# set default value from param-file
|
|
$Control{'op_sys'} = "Linux";
|
|
}
|
|
}
|
|
|
|
|
|
###############################################################
|
|
# Fetches all lines of a query with a single column selected and
|
|
# returns it as an array
|
|
#
|
|
sub FetchAllSQLData( )
|
|
{
|
|
my @res = ();
|
|
|
|
while( MoreSQLData() ){
|
|
push( @res, FetchOneColumn() );
|
|
}
|
|
return( @res );
|
|
}
|
|
|
|
###############################################################
|
|
# Error Handler for Errors in the mail
|
|
#
|
|
# This function can be called multiple within processing one mail and
|
|
# stores the errors found in the Mail. Errors are for example empty
|
|
# required tags, missing required tags and so on.
|
|
#
|
|
# The benefit is, that the mail users get a reply, where all mail errors
|
|
# are reported. The reply mail includes all messages what was wrong and
|
|
# the second mail the user sends can be ok, cause all his faults where
|
|
# reported.
|
|
#
|
|
# BugMailError takes two arguments: The first one is a flag, how heavy
|
|
# the error is:
|
|
#
|
|
# 0 - Its an error, but bugzilla can process the bug. The user should
|
|
# handle that as a warning.
|
|
#
|
|
# 1 - Its a real bug. Bugzilla cant store the bug. The mail has to be
|
|
# resent.
|
|
#
|
|
# 2 - Permission error: The user does not have the permission to send
|
|
# a bug.
|
|
#
|
|
# The second argument is a Text which describs the bug.
|
|
#
|
|
#
|
|
# #
|
|
sub BugMailError($ $ )
|
|
{
|
|
my ( $errflag, $text ) = @_;
|
|
|
|
# On permission error, dont sent all other Errors back -> just quit !
|
|
if( $errflag == 2 ) { # Permission-Error
|
|
Reply( $SenderShort, $Message_ID, "Bugzilla Error", "Permission denied.\n\n" .
|
|
"You do not have the permissions to create a new bug. Sorry.\n" );
|
|
exit;
|
|
}
|
|
|
|
|
|
# Warnings - store for the reply mail
|
|
if( $errflag == 0 ) {
|
|
push( @mailwarnings, $text );
|
|
}
|
|
|
|
# Critical Error
|
|
if( $errflag == 1 ) {
|
|
$critical_err += 1;
|
|
push( @mailerrors, $text );
|
|
}
|
|
}
|
|
|
|
###############################################################
|
|
# getWarningText()
|
|
#
|
|
# getWarningText() returns a reply-ready Textline of all the
|
|
# Warnings in the Mail
|
|
sub getWarningText()
|
|
{
|
|
my $anz = @mailwarnings;
|
|
|
|
my $ret = <<END
|
|
|
|
The Bugzilla Mail Interface found warnings (JFYI):
|
|
|
|
END
|
|
;
|
|
|
|
# Handshake if no warnings at all
|
|
return( "\n\n Your mail was processed without Warnings !\n" ) if( $anz == 0 );
|
|
|
|
# build a text
|
|
$ret .= join( "\n ", @mailwarnings );
|
|
return( horLine() . $ret );
|
|
}
|
|
|
|
sub getErrorText()
|
|
{
|
|
my $anz = @mailerrors;
|
|
|
|
my $ret = <<END
|
|
|
|
************************** ERROR **************************
|
|
|
|
Your request to the Bugzilla mail interface could not be met
|
|
due to errors in the mail. We will find it !
|
|
|
|
|
|
END
|
|
;
|
|
return( "\n\n Your mail was processed without errors !\n") if( $anz == 0 );
|
|
# build a text
|
|
$ret .= join( "\n ", @mailerrors );
|
|
return( $ret );
|
|
}
|
|
|
|
###############################################################
|
|
# generateTemplate
|
|
#
|
|
# This functiuon generates a mail-Template with the
|
|
sub generateTemplate()
|
|
{
|
|
my $w;
|
|
my $ret;
|
|
|
|
# Required Labels
|
|
$ret =<<EOF
|
|
|
|
|
|
You may want to use this template to resend your mail. Please fill in the missing
|
|
keys.
|
|
|
|
_____ snip _______________________________________________________________________
|
|
|
|
EOF
|
|
;
|
|
foreach ( @RequiredLabels ) {
|
|
$w = "";
|
|
$w = $Control{$_} if defined( $Control{ $_ } );
|
|
$ret .= sprintf( " \@%-15s: %s\n", $_, $w );
|
|
}
|
|
|
|
$ret .= "\n";
|
|
# Allowed Labels
|
|
foreach( @AllowedLabels ) {
|
|
next if( /reporter/ ); # Reporter is not a valid label
|
|
next if( /assigned_to/ ); # Assigned to is just a number
|
|
if( defined( $Control{ $_ } ) && lsearch( \@RequiredLabels, $_ ) == -1 ) {
|
|
$ret .= sprintf( " \@%-15s: %s\n", $_, $Control{ $_ } );
|
|
}
|
|
}
|
|
|
|
if( $Body eq "" ) {
|
|
$ret .= <<END
|
|
|
|
< the bug-description follows here >
|
|
|
|
_____ snip _______________________________________________________________________
|
|
|
|
END
|
|
; } else {
|
|
$ret .= "\n" . $Body;
|
|
}
|
|
|
|
return( $ret );
|
|
|
|
}
|
|
###############################################################
|
|
# groupBitToString( $ )
|
|
# converts a given number back to the groupsetting-names
|
|
# This function accepts single numbers as added bits or
|
|
# Strings with +-Signs
|
|
sub groupBitToString( $ )
|
|
{
|
|
my ($bits) = @_;
|
|
my $type;
|
|
my @bitlist = ();
|
|
my $ret = "";
|
|
|
|
if( $bits =~ /^\d+$/ ) { # only numbers
|
|
$type = 1;
|
|
|
|
} elsif( $bits =~ /^(\s*\d+\s*\+\s*)+/ ) {
|
|
$type = 2;
|
|
} else {
|
|
# Error: unknown format !
|
|
$type = 0;
|
|
}
|
|
|
|
$bits =~ s/\s*//g;
|
|
#
|
|
# Query for groupset-Information
|
|
SendSQL( "Select Bit,Name, Description from groups where isbuggroup=1" );
|
|
my @line;
|
|
while( MoreSQLData() ){
|
|
@line = FetchSQLData();
|
|
|
|
if( $type == 1 ) {
|
|
if( ((0+$bits) & (0+$line[0])) == 0+$line[0] ) {
|
|
$ret .= sprintf( "%s ", $line[1] );
|
|
}
|
|
} elsif( $type == 2 ) {
|
|
if( $bits =~ /$line[0]/ ) {
|
|
$ret .= sprintf( "%s ", $line[1] );
|
|
}
|
|
}
|
|
}
|
|
|
|
return( $ret );
|
|
}
|
|
|
|
|
|
|
|
#------------------------------
|
|
#
|
|
# dump_entity ENTITY, NAME
|
|
#
|
|
# Recursive routine for parsing a mime coded mail.
|
|
# One mail may contain more than one mime blocks, which need to be
|
|
# handled. Therefore, this function is called recursively.
|
|
#
|
|
# It gets the for bugzilla important information from the mailbody and
|
|
# stores them into the global attachment-list @attachments. The attachment-list
|
|
# is needed in storeAttachments.
|
|
#
|
|
sub dump_entity {
|
|
my ($entity, $name) = @_;
|
|
defined($name) or $name = "'anonymous'";
|
|
my $IO;
|
|
|
|
|
|
# Output the body:
|
|
my @parts = $entity->parts;
|
|
if (@parts) { # multipart...
|
|
my $i;
|
|
foreach $i (0 .. $#parts) { # dump each part...
|
|
dump_entity($parts[$i], ("$name, part ".(1+$i)));
|
|
}
|
|
} else { # single part...
|
|
|
|
# Get MIME type, and display accordingly...
|
|
my $msg_part = $entity->head->get( 'Content-Disposition' );
|
|
|
|
$msg_part ||= "";
|
|
|
|
my ($type, $subtype) = split('/', $entity->head->mime_type);
|
|
my $body = $entity->bodyhandle;
|
|
my ($data, $on_disk );
|
|
|
|
if( $msg_part =~ /^attachment/ ) {
|
|
# Attached File
|
|
my $des = $entity->head->get('Content-Description');
|
|
$des ||= "";
|
|
|
|
if( defined( $body->path )) { # Data is on disk
|
|
$on_disk = 1;
|
|
$data = $body->path;
|
|
|
|
} else { # Data is in core
|
|
$on_disk = 0;
|
|
$data = $body->as_string;
|
|
}
|
|
push ( @attachments, [ $data, $entity->head->mime_type, $on_disk, $des ] );
|
|
} else {
|
|
# Real Message
|
|
if ($type =~ /^(text|message)$/) { # text: display it...
|
|
if ($IO = $body->open("r")) {
|
|
$Body .= $_ while (defined($_ = $IO->getline));
|
|
$IO->close;
|
|
} else { # d'oh!
|
|
print "$0: couldn't find/open '$name': $!";
|
|
}
|
|
} else { print "Oooops - no Body !\n"; }
|
|
}
|
|
}
|
|
}
|
|
|
|
###############################################################
|
|
# sub extractControls
|
|
###############################################################
|
|
#
|
|
# This sub parses the message Body and filters the control-keys.
|
|
# Attention: Global hash Controls affected
|
|
#
|
|
sub extractControls( $ )
|
|
{
|
|
my ($body) = @_;
|
|
my $backbody = "";
|
|
|
|
my @lbody = split( /\n/, $body );
|
|
|
|
# In restricted mode, all lines before the first keyword
|
|
# are skipped.
|
|
if( $restricted ) {
|
|
while( $lbody[0] =~ /^\s*\@.*/ ){ shift( @lbody );}
|
|
}
|
|
|
|
# Filtering for keys
|
|
foreach( @lbody ) {
|
|
if( /^\s*\@description/ ) {
|
|
s/\s*\@description//;
|
|
$backbody .= $_;
|
|
} elsif( /^\s*\@(.*?)(?:\s*=\s*|\s*:\s*|\s+)(.*?)\s*$/ ) {
|
|
$Control{lc($1)} = $2;
|
|
} else {
|
|
$backbody .= "$_" . "\n";
|
|
}
|
|
}
|
|
|
|
# thats it.
|
|
return( $backbody );
|
|
}
|
|
|
|
###############################################################
|
|
# Main starts here
|
|
###############################################################
|
|
#
|
|
# Commandline switches:
|
|
# -t: test mode - no DB-Inserts
|
|
foreach( @ARGV ) {
|
|
$restricted = 1 if ( /-r/ );
|
|
$test = 1 if ( /-t/ );
|
|
}
|
|
|
|
#
|
|
# Parsing a mime-message
|
|
#
|
|
if( -t STDIN ) {
|
|
print STDERR <<END
|
|
Bugzilla Mail Interface
|
|
|
|
This scripts reads a mail message through stdin and parses the message,
|
|
for to insert a bug to bugzilla.
|
|
|
|
Options
|
|
-t: Testmode - No insert to the DB, but logfile
|
|
-r: restricted mode - all lines before the keys in the mail are skipped
|
|
|
|
END
|
|
;
|
|
exit;
|
|
}
|
|
|
|
|
|
# Create a new MIME parser:
|
|
my $parser = new MIME::Parser;
|
|
|
|
# Create and set the output directory:
|
|
# FIXME: There should be a $BUGZILLA_HOME variable (SML)
|
|
(-d "../data/mimedump-tmp") or mkdir "../data/mimedump-tmp",0755 or die "mkdir: $!";
|
|
(-w "../data/mimedump-tmp") or die "can't write to directory";
|
|
|
|
$parser->output_dir("../data/mimedump-tmp");
|
|
|
|
# Read the MIME message:
|
|
my $entity = $parser->read(\*STDIN) or die "couldn't parse MIME stream";
|
|
$entity->remove_sig(10); # Removes the signature in the last 10 lines
|
|
|
|
# Getting values from parsed mail
|
|
my $Sender = $entity->get( 'From' );
|
|
$Sender ||= $entity->get( 'Reply-To' );
|
|
$Message_ID = $entity->get( 'Message-Id' );
|
|
|
|
die (" *** Cant find Sender-adress in sent mail ! ***\n" ) unless defined( $Sender );
|
|
chomp( $Sender );
|
|
chomp( $Message_ID );
|
|
|
|
ConnectToDatabase();
|
|
|
|
$SenderShort = $Sender;
|
|
$SenderShort =~ s/^.*?([a-zA-Z0-9_.-]+?\@[a-zA-Z0-9_.-]+\.[a-zA-Z0-9_.-]+).*$/$1/;
|
|
|
|
$SenderShort = findUser($SenderShort);
|
|
|
|
print "SenderShort is $SenderShort\n";
|
|
if (!defined($SenderShort)) {
|
|
$SenderShort = $Sender;
|
|
$SenderShort =~ s/^.*?([a-zA-Z0-9_.-]+?\@[a-zA-Z0-9_.-]+\.[a-zA-Z0-9_.-]+).*$/$1/;
|
|
}
|
|
print "The sendershort is now $SenderShort\n";
|
|
|
|
my $Subject = "";
|
|
$Subject = $entity->get( 'Subject' );
|
|
chomp( $Subject );
|
|
|
|
# Get all the attachments
|
|
dump_entity($entity);
|
|
# print $Body;
|
|
$Body = extractControls( $Body ); # fills the Control-Hash
|
|
|
|
if( $test ) {
|
|
foreach (keys %Control ) {
|
|
print "$_ => $Control{$_}\n";
|
|
}
|
|
}
|
|
|
|
$Control{'short_desc'} ||= $Subject;
|
|
#
|
|
# * Mailparsing finishes here *
|
|
#
|
|
|
|
######################################################################
|
|
# Now a lot of Checks of the given Labels start.
|
|
# Check Control-Labels
|
|
# not: reporter !
|
|
@AllowedLabels = ("product", "version", "rep_platform",
|
|
"bug_severity", "priority", "op_sys", "assigned_to",
|
|
"bug_status", "bug_file_loc", "short_desc", "component",
|
|
"status_whiteboard", "target_milestone", "groupset",
|
|
"qa_contact");
|
|
#my @AllowedLabels = qw{Summary priority platform assign};
|
|
foreach (keys %Control) {
|
|
if ( lsearch( \@AllowedLabels, $_) < 0 ) {
|
|
BugMailError( 0, "You sent a unknown label: " . $_ );
|
|
}
|
|
}
|
|
|
|
push( @AllowedLabels, "reporter" );
|
|
$Control{'reporter'} = $SenderShort;
|
|
|
|
# Check required Labels - not all labels are required, because they could be generated
|
|
# from the given information
|
|
# Just send a warning- the error-Flag will be set later
|
|
@RequiredLabels = qw{product version component short_desc};
|
|
foreach my $Label (@RequiredLabels) {
|
|
if ( ! defined $Control{$Label} ) {
|
|
BugMailError( 0, "You were missing a required label: \@$Label\n" );
|
|
next;
|
|
}
|
|
|
|
if( $Control{$Label} =~ /^\s*$/ ) {
|
|
BugMailError( 0, "One of your required labels is empty: $Label" );
|
|
next;
|
|
}
|
|
}
|
|
|
|
if ( $Body =~ /^\s*$/s ) {
|
|
BugMailError( 1, "You sent a completely empty body !" );
|
|
}
|
|
|
|
|
|
# umask 0;
|
|
|
|
# Check Permissions ...
|
|
if (! CheckPermissions("CreateBugs", $SenderShort ) ) {
|
|
BugMailError( 2, "Permission denied.\n\n" .
|
|
"You do not have the permissions to create a new bug. Sorry.\n" );
|
|
}
|
|
|
|
# Set QA
|
|
SendSQL("select initialqacontact from components where program=" .
|
|
SqlQuote($Control{'product'}) .
|
|
" and value=" . SqlQuote($Control{'component'}));
|
|
my $qacontact = FetchOneColumn();
|
|
if (defined $qacontact && $qacontact !~ /^\s*$/) {
|
|
#$Control{'qa_contact'} = DBNameToIdAndCheck($qacontact, 1);
|
|
$Control{'qa_contact'} = DBname_to_id($qacontact);
|
|
|
|
if ( ! $Control{'qa_contact'} ) {
|
|
BugMailError( 0, "Could not resolve qa_contact !\n" );
|
|
}
|
|
|
|
#push(@bug_fields, "qa_contact");
|
|
}
|
|
|
|
# Set Assigned - assigned_to depends on the product, cause initialowner
|
|
# depends on the product !
|
|
# => first check product !
|
|
# Product
|
|
my @all_products = ();
|
|
# set to the default product. If the default product is empty, this has no effect
|
|
my $Product = $DEFAULT_PRODUCT;
|
|
$Product = CheckProduct( $Control{'product'} ) if( defined( $Control{ 'product'} ));
|
|
|
|
if ( $Product eq "" ) {
|
|
my $Text = "You didnt send a value for the required key \@product !\n\n";
|
|
|
|
$Text = "You sent the invalid product \"$Control{'product'}\"!\n\n"
|
|
if( defined( $Control{ 'product'} ));
|
|
|
|
$Text .= "Valid products are:\n\t";
|
|
|
|
SendSQL("select product from products");
|
|
@all_products = FetchAllSQLData();
|
|
$Text .= join( "\n\t", @all_products ) . "\n\n";
|
|
$Text .= horLine();
|
|
|
|
BugMailError( 1, $Text );
|
|
} else {
|
|
# Fill list @all_products, which is needed in case of component-help
|
|
@all_products = ( $Product );
|
|
$product_valid = 1;
|
|
}
|
|
$Control{'product'} = $Product;
|
|
|
|
#
|
|
# Check the Component:
|
|
#
|
|
|
|
# set to the default component. If the default component is empty, this has no effect
|
|
my $Component = $DEFAULT_COMPONENT;
|
|
|
|
if( defined( $Control{'component' } )) {
|
|
$Component = CheckComponent( $Control{'product'}, $Control{'component'} );
|
|
}
|
|
|
|
if ( $Component eq "" ) {
|
|
|
|
my $Text = "You did not send a value for the required key \@component!\n\n";
|
|
|
|
if( defined( $Control{ 'component' } )) {
|
|
$Text = "You sent the invalid component \"$Control{'component'}\" !\n";
|
|
}
|
|
|
|
#
|
|
# Attention: If no product was sent, the user needs info for all components of all
|
|
# products -> big reply mail :)
|
|
# if a product was sent, only reply the components of the sent product
|
|
my @val_components = ();
|
|
foreach my $prod ( @all_products ) {
|
|
$Text .= "\nValid components for product `$prod' are: \n\t";
|
|
|
|
SendSQL("select value from components where program=" . SqlQuote( $prod ) . "");
|
|
@val_components = FetchAllSQLData();
|
|
|
|
$Text .= join( "\n\t", @val_components ) . "\n";
|
|
}
|
|
|
|
# Special: if there is a valid product, maybe it has only one component -> use it !
|
|
#
|
|
my $amount_of_comps = @val_components;
|
|
if( $product_valid && $amount_of_comps == 1 ) {
|
|
$Component = $val_components[0];
|
|
|
|
$Text .= " * You did not send a component, but a valid product " . SqlQuote( $Product ) . ".\n";
|
|
$Text .= " * This product only has one component ". SqlQuote( $Component ) .".\n" .
|
|
" * This component was set by bugzilla for submitting the bug.\n\n";
|
|
BugMailError( 0, $Text ); # No blocker
|
|
|
|
} else { # The component is really buggy :(
|
|
$Text .= horLine();
|
|
BugMailError( 1, $Text );
|
|
}
|
|
}
|
|
$Control{'component'} = $Component;
|
|
|
|
|
|
#
|
|
# Check assigned_to
|
|
# if no assigned_to was given, generate it from the product-DB
|
|
my $forceAssignedOK = 0;
|
|
if ( (! defined($Control{'assigned_to'}) )
|
|
|| $Control{'assigned_to'} =~ /^\s*$/ ) {
|
|
SendSQL("select initialowner from components where program=" .
|
|
SqlQuote($Control{'product'}) .
|
|
" and value=" . SqlQuote($Control{'component'}));
|
|
$Control{'assigned_to'} = FetchOneColumn();
|
|
$forceAssignedOK = 1;
|
|
}
|
|
|
|
|
|
# Recode Names
|
|
$Control{'assigned_to'} = DBname_to_id($Control{'assigned_to'}, $forceAssignedOK);
|
|
|
|
if ( $Control{'assigned_to'} == 0 ) {
|
|
my $Text = "Could not resolve key \@assigned_to !\n" .
|
|
"If you do NOT send a value for assigned_to, the bug will be assigned to\n" .
|
|
"the qa-contact for the product and component.\n";
|
|
$Text .= "This works only if product and component are OK. \n"
|
|
. horLine();
|
|
|
|
BugMailError( 1, $Text );
|
|
}
|
|
|
|
|
|
$Control{'reporter'} = DBname_to_id($Control{'reporter'});
|
|
if ( ! $Control{'reporter'} ) {
|
|
BugMailError( 1, "Could not resolve reporter !\n" );
|
|
}
|
|
|
|
### Set default values
|
|
CheckPriority( );
|
|
CheckSeverity( );
|
|
CheckPlatform( );
|
|
CheckSystem( );
|
|
# CheckArea();
|
|
|
|
### Check values ...
|
|
# Version
|
|
my $Version = "";
|
|
$Version = CheckVersion( $Control{'product'}, $Control{'version'} ) if( defined( $Control{'version'}));
|
|
if ( $Version eq "" ) {
|
|
my $Text = "You did not send a value for the required key \@version!\n\n";
|
|
|
|
if( defined( $Control{'version'})) {
|
|
my $Text = "You sent the invalid version \"$Control{'version'}\"!\n";
|
|
}
|
|
|
|
my $anz_versions;
|
|
my @all_versions;
|
|
# Assemble help text
|
|
foreach my $prod ( @all_products ) {
|
|
$Text .= "Valid versions for product " . SqlQuote( $prod ) . " are: \n\t";
|
|
|
|
SendSQL("select value from versions where program=" . SqlQuote( $prod ) . "");
|
|
@all_versions = FetchAllSQLData();
|
|
$anz_versions = @all_versions;
|
|
$Text .= join( "\n\t", @all_versions ) . "\n" ;
|
|
|
|
}
|
|
|
|
# Check if we could use the only version
|
|
if( $anz_versions == 1 && $product_valid ) {
|
|
$Version = $all_versions[0];
|
|
# Fine, there is only one version string
|
|
$Text .= " * You did not send a version, but a valid product " . SqlQuote( $Product ) . ".\n";
|
|
$Text .= " * This product has has only the one version ". SqlQuote( $Version) .".\n" .
|
|
" * This version was set by bugzilla for submitting the bug.\n\n";
|
|
$Text .= horLine();
|
|
BugMailError( 0, $Text ); # No blocker
|
|
} else {
|
|
$Text .= horLine();
|
|
BugMailError( 1, $Text );
|
|
}
|
|
|
|
}
|
|
|
|
$Control{'version'} = $Version;
|
|
|
|
# GroupsSet: Protections for Bug info. This paramter controls the visiblility of the
|
|
# given bug. An Error in the given Buggroup is not a blocker, a default is taken.
|
|
#
|
|
# The GroupSet is accepted in three ways: As single number like 65536
|
|
# As added numbers like 65536 + 6 +8
|
|
# As literals linked with whitespaces, plus-signs or kommas
|
|
#
|
|
my $GroupSet = "";
|
|
$GroupSet = $Control{'groupset'} if( defined( $Control{ 'groupset' }));
|
|
#
|
|
# Fetch the default value for groupsetting
|
|
SendSQL("select bit from groups where name=" . SqlQuote( "ReadInternal" ));
|
|
my $default_group = FetchOneColumn();
|
|
|
|
if( $GroupSet eq "" ) {
|
|
# To bad: Groupset does not contain anything -> set to default
|
|
$GroupSet = $default_group;
|
|
#
|
|
# Give the user a hint
|
|
my $Text = "You did not send a value for optional key \@groupset, which controls\n";
|
|
$Text .= "the Permissions of the bug. It was set to a default value 'Internal Bug'\n";
|
|
$Text .= "Probably the QA will change that if desired.\n";
|
|
|
|
BugMailError( 0, $Text );
|
|
} elsif( $GroupSet =~ /^\d+$/ ) {
|
|
# Numerical Groups (no +-signs), the GroupSet must be the sum of the bits
|
|
#
|
|
my $grp_num = $GroupSet;
|
|
# print "Numeric: $GroupSet\n";
|
|
SendSQL("select bit from groups where isbuggroup=1 order by bit");
|
|
my @Groups = FetchAllSQLData();
|
|
|
|
# DANGEROUS: This code implies, that perl *CAN* cope with large numbers
|
|
# Its probably better to allow only one default-group when mailing !
|
|
my $Val = "0";
|
|
foreach ( @Groups ) {
|
|
# print 0+$grp_num & 0+$_ , "\n";
|
|
if ( ( (0+$grp_num) & (0+$_) ) == 0+$_ ) {
|
|
$Val .= sprintf( "+%d", $_ );
|
|
}
|
|
}
|
|
if( $Val eq "0" ) {
|
|
# No valid group found
|
|
my $Text = "The number you sent for the groupset of the bug was wrong.\n" .
|
|
"It was not the sum of valid bits, which are:\n\t";
|
|
$Text .= join( "\n\t", @Groups ) . "\n";
|
|
$Text .= "The groupset for your bug is set to default $default_group, which\n" .
|
|
"means 'ReadInternal'";
|
|
|
|
BugMailError( 0, $Text );
|
|
$GroupSet = $default_group;
|
|
} else {
|
|
$GroupSet = $Val;
|
|
}
|
|
|
|
} elsif( $GroupSet =~ /^(\s*\d+\s*\+\s*)+/ ) {
|
|
#
|
|
# Groupset given as String with added numbers like 65536+131072
|
|
# The strings are splitted and checked if the numbers are in the DB
|
|
my @bits = split( /\s*\+\s*/, $GroupSet );
|
|
my $new_groupset = "0";
|
|
# Get all bits for groupsetting
|
|
SendSQL("select bit from groups where isbuggroup=1" );
|
|
my @db_bits = FetchAllSQLData();
|
|
|
|
# ... and check, if the given bits and the one in the DB fit together
|
|
foreach my $bit ( @bits ) {
|
|
# print "The Bit is: $bit \n";
|
|
if( lsearch( \@db_bits, $bit ) == -1 ) {
|
|
# Bit not found !
|
|
my $Text = "Checking the Group-Settings: You sent the Groupset-Bit $bit\n" .
|
|
"which is not a valid Groupset-Bit. It will be scipped !\n\n";
|
|
BugMailError( 0, $Text );
|
|
} else {
|
|
# Cool bit, add to the result-String
|
|
$new_groupset .= "+" . $bit;
|
|
}
|
|
}
|
|
|
|
# Is the new-String larger than 0
|
|
if( $new_groupset eq "0" ) {
|
|
$new_groupset = $default_group;
|
|
|
|
my $Text = "All given Groupsetting-Bits are invalid. Setting Groupsetting to\n" .
|
|
"default-Value $new_groupset, what means 'ReadInternal'\n\n";
|
|
|
|
BugMailError( 0, $Text );
|
|
}
|
|
# Restore to Groupset-Variable
|
|
$GroupSet = $new_groupset;
|
|
|
|
} else {
|
|
# literal e.g. 'ReadInternal'
|
|
my $Value = "0";
|
|
my $gserr = 0;
|
|
my $Text = "";
|
|
|
|
#
|
|
# Split literal Groupsettings either on Whitespaces, +-Signs or ,
|
|
# Then search for every Literal in the DB - col name
|
|
foreach ( split /\s+|\s*\+\s*|\s*,\s*/, $GroupSet ) {
|
|
SendSQL("select bit, Name from groups where name=" . SqlQuote($_));
|
|
my( $bval, $bname ) = FetchSQLData();
|
|
|
|
if( defined( $bname ) && $_ eq $bname ) {
|
|
$Value .= sprintf( "+%d", $bval );
|
|
} else {
|
|
$Text .= "You sent the wrong GroupSet-String $_\n";
|
|
$gserr = 1;
|
|
}
|
|
}
|
|
#
|
|
# Give help if wrong GroupSet-String came
|
|
if( $gserr > 0 ) {
|
|
# There happend errors
|
|
$Text .= "Here are all valid literal Groupsetting-strings:\n\t";
|
|
SendSQL( "select name from groups where isbuggroup=1" );
|
|
$Text .= join( "\n\t", FetchAllSQLData()) . "\n";
|
|
BugMailError( 0, $Text );
|
|
}
|
|
|
|
#
|
|
# Check if anything was right, if not -> set default
|
|
if( $Value eq "0" ) {
|
|
$Value = $default_group;
|
|
$Text .= "\nThe group will be set to $default_group, what means 'ReadInternal'\n\n";
|
|
}
|
|
|
|
$GroupSet = $Value;
|
|
} # End of checking groupsets
|
|
|
|
$Control{'groupset'} = $GroupSet;
|
|
|
|
# ###################################################################################
|
|
# Checking is finished
|
|
#
|
|
|
|
# Check used fields
|
|
my @used_fields;
|
|
|
|
foreach my $f (@AllowedLabels) {
|
|
if ((exists $Control{$f}) && ($Control{$f} !~ /^\s*$/ )) {
|
|
push (@used_fields, $f);
|
|
}
|
|
}
|
|
|
|
#
|
|
# Creating the query for inserting the bug
|
|
# -> this should only be done, if there was no critical error before
|
|
if( $critical_err == 0 )
|
|
{
|
|
|
|
my $reply = <<END
|
|
|
|
+---------------------------------------------------------------------------+
|
|
B U G Z I L L A - M A I L - I N T E R F A C E
|
|
+---------------------------------------------------------------------------+
|
|
|
|
Your Bugzilla Mail Interface request was successfull.
|
|
|
|
END
|
|
;
|
|
|
|
$reply .= "Your Bug-ID is ";
|
|
my $reporter = "";
|
|
|
|
my $query = "insert into bugs (\n" . join(",\n", @used_fields ) .
|
|
", bug_status, creation_ts) values ( ";
|
|
|
|
my $tmp_reply = "These values were stored by bugzilla:\n";
|
|
my $val;
|
|
foreach my $field (@used_fields) {
|
|
if( $field eq "groupset" ) {
|
|
$query .= $Control{$field} . ",\n";
|
|
} else {
|
|
$query .= SqlQuote($Control{$field}) . ",\n";
|
|
}
|
|
|
|
$val = $Control{ $field };
|
|
|
|
$val = DBID_to_name( $val ) if( $field =~ /reporter|assigned_to|qa_contact/ );
|
|
$val = groupBitToString( $val ) if( $field =~ /groupset/ );
|
|
|
|
$tmp_reply .= sprintf( " \@%-15s = %-15s\n", $field, $val );
|
|
|
|
if ($field eq "reporter") {
|
|
$reporter = $val;
|
|
}
|
|
}
|
|
|
|
$tmp_reply .= " ... and your error-description !\n";
|
|
|
|
my $comment = $Body;
|
|
$comment =~ s/\r\n/\n/g; # Get rid of windows-style line endings.
|
|
$comment =~ s/\r/\n/g; # Get rid of mac-style line endings.
|
|
$comment = trim($comment);
|
|
|
|
SendSQL("SELECT now()");
|
|
my $bug_when = FetchOneColumn();
|
|
|
|
$query .= SqlQuote( "NEW" ) . ", \'$bug_when\')\n";
|
|
# $query .= SqlQuote( "NEW" ) . ", now(), " . SqlQuote($comment) . " )\n";
|
|
|
|
SendSQL("SELECT userid FROM profiles WHERE login_name=\'$reporter\'");
|
|
my $userid = FetchOneColumn();
|
|
|
|
my $id;
|
|
|
|
if( ! $test ) {
|
|
SendSQL($query);
|
|
|
|
SendSQL("select LAST_INSERT_ID()");
|
|
$id = FetchOneColumn();
|
|
|
|
my $long_desc_query = "INSERT INTO longdescs SET bug_id=$id, who=$userid, bug_when=\'$bug_when\', thetext=" . SqlQuote($comment);
|
|
SendSQL($long_desc_query);
|
|
|
|
# Cool, the mail was successfull
|
|
system("cd .. ; ./processmail $id '$Sender'");
|
|
} else {
|
|
$id = 0xFFFF; # TEST !
|
|
print "\n-------------------------------------------------------------------------\n";
|
|
print "$query\n";
|
|
}
|
|
|
|
#
|
|
# handle Attachments
|
|
#
|
|
my $attaches = storeAttachments( $id, $Control{'reporter'} );
|
|
$tmp_reply .= "\n\tYou sent $attaches attachment(s). \n" if( $attaches > 0 );
|
|
|
|
$reply .= $id . "\n\n" . $tmp_reply . "\n" . getWarningText();
|
|
|
|
$entity->purge(); # Removes all temp files
|
|
|
|
#
|
|
# Send the 'you did it'-reply
|
|
Reply( $SenderShort, $Message_ID,"Bugzilla success (ID $id)", $reply );
|
|
|
|
} else {
|
|
# There were critical errors in the mail - the bug couldnt be inserted. !
|
|
my $errreply = <<END
|
|
|
|
+---------------------------------------------------------------------------+
|
|
B U G Z I L L A - M A I L - I N T E R F A C E
|
|
+---------------------------------------------------------------------------+
|
|
|
|
END
|
|
;
|
|
|
|
$errreply .= getErrorText() . getWarningText() . generateTemplate();
|
|
|
|
Reply( $SenderShort, $Message_ID, "Bugzilla Error", $errreply );
|
|
|
|
# print getErrorText();
|
|
# print getWarningText();
|
|
# print generateTemplate();
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
exit;
|
|
|