processmail 11.3 KB
Newer Older
1 2
#!/usr/bonsaitools/bin/perl -w
# -*- Mode: perl; indent-tabs-mode: nil -*-
terry%netscape.com's avatar
terry%netscape.com committed
3
#
4 5 6 7 8 9 10 11 12 13
# 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.
#
terry%netscape.com's avatar
terry%netscape.com committed
14
# The Original Code is the Bugzilla Bug Tracking System.
15
#
terry%netscape.com's avatar
terry%netscape.com committed
16
# The Initial Developer of the Original Code is Netscape Communications
17 18 19 20
# Corporation. Portions created by Netscape are
# Copyright (C) 1998 Netscape Communications Corporation. All
# Rights Reserved.
#
21 22
# Contributor(s): Terry Weissman <terry@mozilla.org>,
# Bryce Nesbitt <bryce-mozilla@nextbus.com>
23 24 25

# To recreate the shadow database,  run "processmail regenerate" .

26 27
use diagnostics;
use strict;
terry%netscape.com's avatar
terry%netscape.com committed
28

29
require "globals.pl";
terry%netscape.com's avatar
terry%netscape.com committed
30

31
$| = 1;
terry%netscape.com's avatar
terry%netscape.com committed
32

33 34 35
umask(0);

$::lockcount = 0;
36 37
my $regenerate = 0;
my $nametoexclude = "";
38 39 40 41 42 43 44 45 46 47 48 49 50 51

sub Lock {
    if ($::lockcount <= 0) {
        $::lockcount = 0;
        if (!open(LOCKFID, ">>data/maillock")) {
            mkdir "data", 0777;
            chmod 0777, "data";
            open(LOCKFID, ">>data/maillock") || die "Can't open lockfile.";
        }
        my $val = flock(LOCKFID,2);
        if (!$val) { # '2' is magic 'exclusive lock' const.
            print "Lock failed: $val\n";
        }
        chmod 0666, "data/maillock";
terry%netscape.com's avatar
terry%netscape.com committed
52
    }
53
    $::lockcount++;
terry%netscape.com's avatar
terry%netscape.com committed
54 55
}

56 57 58 59 60 61 62
sub Unlock {
    $::lockcount--;
    if ($::lockcount <= 0) {
        flock(LOCKFID,8);       # '8' is magic 'unlock' const.
        close LOCKFID;
    }
}
terry%netscape.com's avatar
terry%netscape.com committed
63

64 65 66 67 68 69 70 71 72
sub FileSize {
    my ($filename) = (@_);
    my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,
        $atime,$mtime,$ctime,$blksize,$blocks)
        = stat($filename);
    if (defined $size) {
        return $size;
    }
    return -1;
terry%netscape.com's avatar
terry%netscape.com committed
73 74 75
}


76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104

sub Different {
    my ($file1, $file2) = (@_);
    my $size1 = FileSize($file1);
    my $size2 = FileSize($file2);
    if ($size1 != $size2) {
        return 1;
    }
    open(FID1, "<$file1") || die "Can't open $file1";
    open(FID2, "<$file2") || die "Can't open $file2";
    my $d1; 
    my $d2; 
    if (read(FID1, $d1, $size1) ne $size1) {
        die "Can't read $size1 bytes from $file1";
    }
    if (read(FID2, $d2, $size2) ne $size2) {
        die "Can't read $size2 bytes from $file2";
    }
    close FID1;
    close FID2;
    return ($d1 ne $d2);
}


sub DescCC {
    my ($cclist) = (@_);
    if (scalar(@$cclist) <= 0) {
        return "";
    }
105
    return "Cc: " . join(", ", @$cclist) . "\n";
106 107 108
}


109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124
sub DescDependencies {
    my ($id) = (@_);
    if (!Param("usedependencies")) {
        return "";
    }
    my $result = "";
    my $me = "blocked";
    my $target = "dependson";
    my $title = "BugsThisDependsOn";
    for (1..2) {
        SendSQL("select $target from dependencies where $me = $id order by $target");
        my @list;
        while (MoreSQLData()) {
            push(@list, FetchOneColumn());
        }
        if (@list) {
125
            my @verbose;
126
            my $count = 0;
127
            foreach my $i (@list) {
128 129 130 131
                SendSQL("select bug_status, resolution from bugs where bug_id = $i");
                my ($bug_status, $resolution) = (FetchSQLData());
                my $desc;
                if ($bug_status eq "NEW" || $bug_status eq "ASSIGNED" ||
132 133
                    $bug_status eq "REOPENED") {
                    $desc = "";
134 135
                } else {
                    $desc = "[$resolution]";
136
                }
137 138 139 140 141 142 143
                push(@verbose, $i . "$desc");
                $count++;
            }
            if ($count > 5) {
                $result .= "$title: Big list (more than 5) has been omitted\n";
            } else {
                $result .= "$title: " . join(', ', @verbose) . "\n";
144
            }
145 146 147 148 149 150 151 152 153 154 155
        }
        my $tmp = $me;
        $me = $target;
        $target = $tmp;
        $title = "OtherBugsDependingOnThis";
    }
    return $result;
}



156 157 158
sub GetBugText {
    my ($id) = (@_);
    undef %::bug;
terry%netscape.com's avatar
terry%netscape.com committed
159
    
160 161
    my @collist = ("bug_id", "product", "version", "rep_platform", "op_sys",
                   "bug_status", "resolution", "priority", "bug_severity",
162
                   "assigned_to", "reporter", "bug_file_loc",
163
                   "short_desc", "component", "qa_contact", "target_milestone",
164
                   "status_whiteboard", "groupset");
165 166 167 168 169 170 171 172 173

    my $query = "select " . join(", ", @collist) .
        " from bugs where bug_id = $id";

    SendSQL($query);

    my @row;
    if (!(@row = FetchSQLData())) {
        return "";
terry%netscape.com's avatar
terry%netscape.com committed
174
    }
175 176 177 178 179
    foreach my $field (@collist) {
        $::bug{$field} = shift @row;
        if (!defined $::bug{$field}) {
            $::bug{$field} = "";
        }
terry%netscape.com's avatar
terry%netscape.com committed
180 181
    }

182 183
    $::bug{'assigned_to'} = DBID_to_name($::bug{'assigned_to'});
    $::bug{'reporter'} = DBID_to_name($::bug{'reporter'});
184 185 186 187 188 189 190 191 192 193 194 195 196 197 198
    my $qa_contact = "";
    my $target_milestone = "";
    my $status_whiteboard = "";
    if (Param('useqacontact') && $::bug{'qa_contact'} > 0) {
        $::bug{'qa_contact'} = DBID_to_name($::bug{'qa_contact'});
        $qa_contact = "QAContact: $::bug{'qa_contact'}\n";
    } else {
        $::bug{'qa_contact'} = "";
    }
    if (Param('usetargetmilestone') && $::bug{'target_milestone'} ne "") {
        $target_milestone = "TargetMilestone: $::bug{'target_milestone'}\n";
    }
    if (Param('usestatuswhiteboard') && $::bug{'status_whiteboard'} ne "") {
        $status_whiteboard = "StatusWhiteboard: $::bug{'status_whiteboard'}\n";
    }
terry%netscape.com's avatar
terry%netscape.com committed
199

200
    $::bug{'long_desc'} = GetLongDescription($id);
terry%netscape.com's avatar
terry%netscape.com committed
201

202 203
    my @cclist;
    @cclist = split(/,/, ShowCcList($id));
204
    my @voterlist;
205 206
    SendSQL("select profiles.login_name from votes, profiles where votes.bug_id = $id and profiles.userid = votes.who");
    while (MoreSQLData()) {
207 208
        my $v = FetchOneColumn();
        push(@voterlist, $v);
209
    }
210 211
    $::bug{'cclist'} = join(',', @cclist);
    $::bug{'voterlist'} = join(',', @voterlist);
terry%netscape.com's avatar
terry%netscape.com committed
212 213 214


    return "Bug\#: $id
215 216 217 218 219 220 221 222 223 224 225
Product: $::bug{'product'}
Version: $::bug{'version'}
Platform: $::bug{'rep_platform'}
OS/Version: $::bug{'op_sys'}
Status: $::bug{'bug_status'}   
Resolution: $::bug{'resolution'}
Severity: $::bug{'bug_severity'}
Priority: $::bug{'priority'}
Component: $::bug{'component'}
AssignedTo: $::bug{'assigned_to'}                            
ReportedBy: $::bug{'reporter'}               
226
$qa_contact$target_milestone${status_whiteboard}URL: $::bug{'bug_file_loc'}
227
" . DescCC(\@cclist) . "Summary: $::bug{'short_desc'}
228
" . DescDependencies($id) . "
229 230
$::bug{'long_desc'}
";
terry%netscape.com's avatar
terry%netscape.com committed
231 232 233 234

}


235
my $didexclude = 0;
236
my %seen;
237
sub fixaddresses {
238
    my ($field, $list) = (@_);
239 240
    my @result;
    foreach my $i (@$list) {
241
        if (!defined $i || $i eq "") {
242 243
            next;
        }
244
        SendSQL("select emailnotification, groupset & $::bug{'groupset'} from profiles where login_name = " .
245
                SqlQuote($i));
246 247 248 249
        my ($emailnotification, $groupset) = (FetchSQLData());
        if ($groupset ne $::bug{'groupset'}) {
            next;
        }
250 251 252 253 254
        if ($emailnotification eq "CConly") {
            if ($field ne "cc") {
                next;
            }
        }
255 256
        if ($emailnotification eq "ExcludeSelfChanges" &&
           (lc($i) eq $nametoexclude)) {
257 258 259 260 261
            $didexclude = 1;
            next;
        }
        
        if (!defined $::nomail{$i} && !defined $seen{$i}) {
262
            push(@result, $i . Param('emailsuffix'));
263
            $seen{$i} = 1;
terry%netscape.com's avatar
terry%netscape.com committed
264 265
        }
    }
266
    return join(", ",  @result);
terry%netscape.com's avatar
terry%netscape.com committed
267 268 269
}


270 271 272 273 274 275 276
sub Log {
    my ($str) = (@_);
    Lock();
    open(FID, ">>data/maillog") || die "Can't write to data/maillog";
    print FID time2str("%D %H:%M", time()) . ": $str\n";
    close FID;
    Unlock();
terry%netscape.com's avatar
terry%netscape.com committed
277 278
}
    
279 280
sub ProcessOneBug {
    my $i = $_[0];
281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299
    my $old = "shadow/$i";
    my $new = "shadow/$i.tmp.$$";
    my $diffs = "shadow/$i.diffs.$$";
    my $verb = "Changed";
    if (!stat($old)) {
        mkdir "shadow", 0777;
        chmod 0777, "shadow";
        open(OLD, ">$old") || die "Couldn't create null $old";
        close OLD;
        $verb = "New";
    }
    my $text = GetBugText($i);
    if ($text eq "") {
        die "Couldn't find bug $i.";
    }
    open(FID, ">$new") || die "Couldn't create $new";
    print FID $text;
    close FID;
    if (Different($old, $new)) {
300
        system("diff -c -b $old $new > $diffs");
301 302
        my $tolist = fixaddresses("to",
                                  [$::bug{'assigned_to'}, $::bug{'reporter'},
303
                                   $::bug{'qa_contact'}]);
304 305 306 307 308
        my @combinedcc;
        foreach my $v (split(/,/, "$::bug{'cclist'},$::bug{'voterlist'}")) {
            push @combinedcc, $v;
        }
        my $cclist = fixaddresses("cc", \@combinedcc);
309
        my $logstr = "Bug $i $verb";
310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326
        if ($tolist ne "" || $cclist ne "") {
            my %substs;

            $substs{"to"} = $tolist;
            $substs{"cc"} = $cclist;
            $substs{"bugid"} = $i;
            $substs{"diffs"} = "";
            open(DIFFS, "<$diffs") || die "Can't open $diffs";
            while (<DIFFS>) {
                $substs{"diffs"} .= $_;
            }
            close DIFFS;
            $substs{"neworchanged"} = $verb;
            $substs{"summary"} = $::bug{'short_desc'};
            my $msg = PerformSubsts(Param("changedmail"), \%substs);

            if (!$regenerate) {
327 328
                # Note: fixaddresses may result in a Cc: only.  This seems
                # harmless.
329
                open(SENDMAIL, "|/usr/lib/sendmail -t") ||
330 331 332
                    die "Can't open sendmail";
                print SENDMAIL $msg;
                close SENDMAIL;
333
                $logstr = "$logstr; mail sent to $tolist, $cclist";
334
                print "<B>Email sent to:</B> $tolist $cclist\n";
335 336 337
                if ($didexclude) {
                    print "<B>Excluding:</B> $nametoexclude (<a href=changepassword.cgi>change your preferences</a> if you wish not to be excluded)\n";
                }
terry%netscape.com's avatar
terry%netscape.com committed
338 339
            }
        }
340 341
        unlink($diffs);
        Log($logstr);
terry%netscape.com's avatar
terry%netscape.com committed
342
    }
343 344 345 346
    rename($new, $old) || die "Can't rename $new to $old";
    chmod 0666, $old;
    if ($regenerate) {
        print "$i ";
terry%netscape.com's avatar
terry%netscape.com committed
347
    }
348
    %seen = ();
terry%netscape.com's avatar
terry%netscape.com committed
349 350
}

351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378
# Code starts here

ConnectToDatabase();
Lock();

if (open(FID, "<data/nomail")) {
    while (<FID>) {
        $::nomail{trim($_)} = 1;
    }
    close FID;
}

if (($#ARGV < 0) || ($#ARGV > 1)) {
    print "Usage error: processmail {bugid} {nametoexclude}\nOr: processmail regenerate\n";
    exit;
}

# To recreate the shadow database,  run "processmail regenerate" .
if ($ARGV[0] eq "regenerate") {
    $regenerate = 1;
    shift @ARGV;
    SendSQL("select bug_id from bugs order by bug_id");
    my @regenerate_list;
    while (my @row = FetchSQLData()) {
        push @regenerate_list, $row[0];
    }
    foreach my $i (@regenerate_list) {
        ProcessOneBug($i);
379 380
        Unlock();
        Lock();
381 382 383 384 385 386
    }
    print("\n");
    exit;
}

if ($#ARGV == 1) {
387
    $nametoexclude = lc($ARGV[1]);
388 389
}

390 391
if ($ARGV[0] eq "rescanall") {
    print "<br> Collecting bug ids...\n";
392
    SendSQL("select bug_id from bugs where to_days(now()) - to_days(delta_ts) <= 2 order by bug_id");
393 394 395 396 397 398 399 400 401 402 403 404
    my @list;
    while (my @row = FetchSQLData()) {
        push @list, $row[0];
    }
    foreach my $id (@list) {
        $ARGV[0] = $id;
        print "<br> Doing bug $id\n";
        ProcessOneBug($ARGV[0]);
    }
} else {
    ProcessOneBug($ARGV[0]);
}
405

406
exit;