574
574
"bts-server=s" => \$btsserver,
575
575
"mutt" => \$opt_mutt,
577
or die "Usage: bts [options]\nRun $progname --help for more details\n";
577
or die "Usage: $progname [options]\nRun $progname --help for more details\n";
579
579
if ($opt_noconf) {
580
die "bts: --no-conf is only acceptable as the first command-line option!\n";
580
die "$progname: --no-conf is only acceptable as the first command-line option!\n";
582
582
if ($opt_help) { bts_help(); exit 0; }
583
583
if ($opt_version) { bts_version(); exit 0; }
606
606
if ($opt_sendmail and $opt_smtphost) {
607
die "bts: --sendmail and --smtp-host mutually exclusive\n";
607
die "$progname: --sendmail and --smtp-host mutually exclusive\n";
608
608
} elsif ($opt_mutt and $opt_sendmail) {
609
die "bts: --sendmail and --mutt mutually exclusive\n";
609
die "$progname: --sendmail and --mutt mutually exclusive\n";
610
610
} elsif ($opt_mutt and $opt_smtphost) {
611
die "bts: --smtp-host and --mutt mutually exclusive\n";
611
die "$progname: --smtp-host and --mutt mutually exclusive\n";
614
614
$smtphost = $opt_smtphost if $opt_smtphost;
651
651
if ($opt_cachemode =~ /^(min|mbox|full)$/) {
652
652
$cachemode=$opt_cachemode;
654
warn "bts: ignoring invalid --cache-mode; must be one of min, mbox, full.\n";
654
warn "$progname: ignoring invalid --cache-mode; must be one of min, mbox, full.\n";
901
901
"m|mbox" => \$sub_mboxmode,
902
902
"mailreader|mail-reader=s" => \$sub_mailreader,
904
or die "bts: unknown options for bugs command\n";
904
or die "$progname: unknown options for bugs command\n";
905
905
@_ = @ARGV; # whatever's left
907
907
if (defined $sub_offlinemode) {
1180
1180
sub bts_close {
1181
1181
my ($bug) = common_close(@_);
1183
bts: Closing $bug as you requested.
1184
Please note that the "bts close" command is deprecated!
1183
$progname: Closing $bug as you requested.
1184
Please note that the "$progname close" command is deprecated!
1185
1185
It is usually better to email nnnnnn-done\@$btsserver with
1186
1186
an informative mail.
1187
1187
Please remember to email $bug-submitter\@$btsserver with
2028
2028
my $request = HTTP::Request->new('GET', $url);
2029
2029
my $response = $ua->request($request);
2030
2030
if (! $response->is_success) {
2031
warn "bts: failed to report $bug as containing spam: " .
2031
warn "$progname: failed to report $bug as containing spam: " .
2032
2032
$response->status_line . "\n";
2106
2106
"q|quiet+" => \$sub_quiet,
2107
2107
"include-resolved!" => \$sub_includeresolved,
2109
or die "bts: unknown options for cache command\n";
2109
or die "$progname: unknown options for cache command\n";
2110
2110
@_ = @ARGV; # whatever's left
2112
2112
if (defined $sub_refreshmode) {
2119
2119
if ($sub_cachemode =~ /^(min|mbox|full)$/) {
2120
2120
($cachemode, $sub_cachemode) = ($sub_cachemode, $cachemode);
2122
warn "bts: ignoring invalid --cache-mode $sub_cachemode;\nmust be one of min, mbox, full.\n";
2122
warn "$progname: ignoring invalid --cache-mode $sub_cachemode;\nmust be one of min, mbox, full.\n";
2125
2125
# This may be a no-op, we don't mind
2130
2130
if (! have_lwp()) {
2131
die "bts: Couldn't run bts cache: $lwp_broken\n";
2131
die "$progname: Couldn't run bts cache: $lwp_broken\n";
2134
2134
if (! -d $cachedir) {
2135
2135
if (! -d dirname($cachedir)) {
2136
2136
mkdir(dirname($cachedir))
2137
or die "bts: couldn't mkdir ".dirname($cachedir).": $!\n";
2137
or die "$progname: couldn't mkdir ".dirname($cachedir).": $!\n";
2139
2139
mkdir($cachedir)
2140
or die "bts: couldn't mkdir $cachedir: $!\n";
2140
or die "$progname: couldn't mkdir $cachedir: $!\n";
2143
2143
download("css/bugs.css");
2176
2176
if (keys %oldbugs) {
2177
2177
tie (%timestamp, "Devscripts::DB_File_Lock", $timestampdb,
2178
2178
O_RDWR()|O_CREAT(), 0600, $DB_HASH, "write")
2179
or die "bts: couldn't open DB file $timestampdb for writing: $!\n"
2179
or die "$progname: couldn't open DB file $timestampdb for writing: $!\n"
2180
2180
if ! tied %timestamp;
2250
2250
tie (%timestamp, "Devscripts::DB_File_Lock", $timestampdb,
2251
2251
O_RDWR()|O_CREAT(), 0600, $DB_HASH, "write")
2252
or die "bts: couldn't open DB file $timestampdb for writing: $!\n"
2252
or die "$progname: couldn't open DB file $timestampdb for writing: $!\n"
2253
2253
if ! tied %timestamp;
2255
2255
if ($toclean =~ /^\d+$/) {
2469
2469
DIR => File::Spec->tmpdir,
2471
2471
open (MAILOUT, ">&", $fh)
2472
or die "bts: writing to temporary file: $!\n";
2472
or die "$progname: writing to temporary file: $!\n";
2474
2474
print MAILOUT $message;
2476
2476
my $mailcmd = $muttcmd;
2477
2477
$mailcmd =~ s/\%([%s])/$1 eq '%' ? '%' : $filename/eg;
2479
exec($mailcmd) or die "bts: unable to start mailclient: $!";
2479
exec($mailcmd) or die "$progname: unable to start mailclient: $!";
2481
2481
elsif (length $smtphost) {
2488
2488
if (have_smtp_ssl) {
2489
2489
$smtp = Net::SMTP::SSL->new($host, Port => $port,
2490
Hello => $smtphelo) or die "bts: failed to open SMTPS connection to $smtphost\n($@)\n";
2490
Hello => $smtphelo) or die "$progname: failed to open SMTPS connection to $smtphost\n($@)\n";
2492
die "bts: Unable to establish SMTPS connection: $smtp_ssl_broken\n($@)\n";
2492
die "$progname: Unable to establish SMTPS connection: $smtp_ssl_broken\n($@)\n";
2495
2495
my ($host, $port) = split(/:/, $smtphost);
2496
2496
$port ||= '25';
2498
2498
$smtp = Net::SMTP->new($host, Port => $port, Hello => $smtphelo)
2499
or die "bts: failed to open SMTP connection to $smtphost\n($@)\n";
2499
or die "$progname: failed to open SMTP connection to $smtphost\n($@)\n";
2501
2501
if ($smtpuser) {
2502
2502
$smtppass = getpass() if not $smtppass;
2503
2503
$smtp->auth($smtpuser, $smtppass)
2504
or die "bts: failed to authenticate to $smtphost\n($@)\n";
2504
or die "$progname: failed to authenticate to $smtphost\n($@)\n";
2506
2506
$smtp->mail($fromaddress)
2507
or die "bts: failed to set SMTP from address $fromaddress\n($@)\n";
2507
or die "$progname: failed to set SMTP from address $fromaddress\n($@)\n";
2508
2508
my @addresses = extract_addresses($to);
2509
2509
push @addresses, extract_addresses($cc);
2510
2510
foreach my $address (@addresses) {
2511
2511
$smtp->recipient($address)
2512
or die "bts: failed to set SMTP recipient $address\n($@)\n";
2512
or die "$progname: failed to set SMTP recipient $address\n($@)\n";
2514
2514
$smtp->data($message)
2515
or die "bts: failed to send message as SMTP DATA\n($@)\n";
2515
or die "$progname: failed to send message as SMTP DATA\n($@)\n";
2517
or die "bts: failed to quit SMTP connection\n($@)\n";
2517
or die "$progname: failed to quit SMTP connection\n($@)\n";
2520
2520
my $pid = open(MAIL, "|-");
2521
2521
if (! defined $pid) {
2522
die "bts: Couldn't fork: $!\n";
2522
die "$progname: Couldn't fork: $!\n";
2524
$SIG{'PIPE'} = sub { die "bts: pipe for $sendmailcmd broke\n"; };
2524
$SIG{'PIPE'} = sub { die "$progname: pipe for $sendmailcmd broke\n"; };
2527
2527
print MAIL $message;
2528
close MAIL or die "bts: sendmail error: $!\n";
2528
close MAIL or die "$progname: sendmail error: $!\n";
2533
2533
exec("/bin/cat")
2534
or die "bts: error running cat: $!\n";
2534
or die "$progname: error running cat: $!\n";
2536
2536
my @mailcmd = split ' ', $sendmailcmd;
2537
2537
push @mailcmd, "-t" if $sendmailcmd =~ /$sendmail_t/;
2539
or die "bts: error running sendmail: $!\n";
2539
or die "$progname: error running sendmail: $!\n";
2628
2628
unless (system("command -v mail >/dev/null 2>&1") == 0) {
2629
die "bts: You need to either set DEBEMAIL or have the mailx/mailutils package\ninstalled to send mail!\n";
2629
die "$progname: You need to either set DEBEMAIL or have the mailx/mailutils package\ninstalled to send mail!\n";
2631
2631
my $pid = open(MAIL, "|-");
2632
2632
if (! defined $pid) {
2633
die "bts: Couldn't fork: $!\n";
2633
die "$progname: Couldn't fork: $!\n";
2635
$SIG{'PIPE'} = sub { die "bts: pipe for mail broke\n"; };
2635
$SIG{'PIPE'} = sub { die "$progname: pipe for mail broke\n"; };
2638
2638
print MAIL $body;
2639
close MAIL or die "bts: mail: $!\n";
2639
close MAIL or die "$progname: mail: $!\n";
2644
2644
exec("/bin/cat")
2645
or die "bts: error running cat: $!\n";
2645
or die "$progname: error running cat: $!\n";
2647
2647
$ccemail =~ s/ //g;
2650
2650
push(@args, "-c", "$ccemail") if $ccemail;
2651
2651
push(@args, "-a", "X-Debbugs-No-Ack: Yes")
2652
2652
if $requestack==0;
2653
exec("mail", @args) or die "bts: error running mail: $!\n";
2653
exec("mail", @args) or die "$progname: error running mail: $!\n";
2727
2727
system "stty echo -cbreak </dev/tty";
2728
die "bts: error enabling stty echo\n" if $?;
2728
die "$progname: error enabling stty echo\n" if $?;
2732
2732
sub extractemail() {
2733
my $thing=shift or die "bts: extract e-mail from what?\n";
2733
my $thing=shift or die "$progname: extract e-mail from what?\n";
2735
2735
if ($thing =~ /^(.*?)\s+<(.*)>\s*$/) {
2750
2750
else { # No $from
2751
2751
unless (system("command -v mail >/dev/null 2>&1") == 0) {
2752
die "bts: You need to either specify an email address (say using DEBEMAIL)\n or have the mailx/mailutils package installed to send mail!\n";
2752
die "$progname: You need to either specify an email address (say using DEBEMAIL)\n or have the mailx/mailutils package installed to send mail!\n";
2754
2754
my $pid = open(MAIL, "|-");
2755
2755
if (! defined $pid) {
2756
die "bts: Couldn't fork: $!\n";
2756
die "$progname: Couldn't fork: $!\n";
2758
$SIG{'PIPE'} = sub { die "bts: pipe for mail broke\n"; };
2758
$SIG{'PIPE'} = sub { die "$progname: pipe for mail broke\n"; };
2761
2761
print MAIL $body;
2762
close MAIL or die "bts: mail: $!\n";
2762
close MAIL or die "$progname: mail: $!\n";
2767
2767
exec("/bin/cat")
2768
or die "bts: error running cat: $!\n";
2768
or die "$progname: error running cat: $!\n";
2770
2770
exec("mail", "-s", $subject, $to)
2771
or die "bts: error running mail: $!\n";
2771
or die "$progname: error running mail: $!\n";
2912
2912
if (! -d $cachedir) {
2913
die "bts: download() called but no cachedir!\n";
2913
die "$progname: download() called but no cachedir!\n";
2916
chdir($cachedir) || die "bts: chdir $cachedir: $!\n";
2916
chdir($cachedir) || die "$progname: chdir $cachedir: $!\n";
2918
2918
if (-f cachefile($thing, $thgopts)) {
2919
2919
($timestamp, $versionstamp) = get_timestamp($thing, $thgopts);
2934
2934
if (! -r mboxfile($thing)) {
2935
2935
$forcedownload = 1;
2936
2936
} elsif ($cachemode eq 'full' and -d $thing) {
2937
opendir DIR, $thing or die "bts: opendir $cachedir/$thing: $!\n";
2937
opendir DIR, $thing or die "$progname: opendir $cachedir/$thing: $!\n";
2938
2938
my @htmlfiles = grep { /^\d+\.html$/ } readdir(DIR);
2940
2940
$forcedownload = 1 unless @htmlfiles;
2986
2986
my $data = $livepage; # work on a copy, not the original
2987
2987
my $cachefile=cachefile($thing,$thgopts);
2988
open (OUT_CACHE, ">$cachefile") or die "bts: open $cachefile: $!\n";
2988
open (OUT_CACHE, ">$cachefile") or die "$progname: open $cachefile: $!\n";
2990
2990
$data = mangle_cache_file($data, $thing, $bug2filename, $timestamp, $charset ? $contenttype : '');
2991
2991
print OUT_CACHE $data;
2992
close OUT_CACHE or die "bts: problems writing to $cachefile: $!\n";
2992
close OUT_CACHE or die "$progname: problems writing to $cachefile: $!\n";
2994
2994
set_timestamp($thing, $thgopts,
2995
2995
$manual ? make_manual($timestamp) : make_automatic($timestamp),
3048
3048
# it's an attachment, must download
3050
3050
if (-f dirname($filename)) {
3051
warn "bts: found file where directory expected; using existing file (" . dirname($filename) . ")\n";
3051
warn "$progname: found file where directory expected; using existing file (" . dirname($filename) . ")\n";
3052
3052
$bug2filename{$msg} = dirname($filename);
3054
3054
$bug2filename{$msg} = $filename;
3098
3098
my $content_length = defined $response->content ?
3099
3099
length($response->content) : 0;
3100
3100
if ($content_length == 0) {
3101
warn "bts: failed to download $ref, skipping\n";
3101
warn "$progname: failed to download $ref, skipping\n";
3113
3113
mkpath(dirname $bug2filename{$msg});
3114
3114
open OUT_CACHE, ">$bug2filename{$msg}"
3115
or die "bts: open cache $bug2filename{$msg}\n";
3115
or die "$progname: open cache $bug2filename{$msg}\n";
3116
3116
print OUT_CACHE $data;
3117
3117
close OUT_CACHE;
3119
warn "bts: failed to download $ref, skipping\n";
3119
warn "$progname: failed to download $ref, skipping\n";
3132
3132
my $temp = shift; # do we wish to store it in cache or in a temp file?
3133
3133
my $mboxfile = mboxfile($thing);
3135
die "bts: trying to download mbox for illegal bug number $thing.\n"
3135
die "$progname: trying to download mbox for illegal bug number $thing.\n"
3136
3136
unless $mboxfile;
3138
3138
if (! have_lwp()) {
3139
die "bts: couldn't run bts --mbox: $lwp_broken\n";
3139
die "$progname: couldn't run bts --mbox: $lwp_broken\n";
3141
3141
init_agent() unless $ua;
3158
3158
# Use filehandle for security
3159
3159
open (OUT_MBOX, ">&", $fh)
3160
or die "bts: writing to temporary file: $!\n";
3160
or die "$progname: writing to temporary file: $!\n";
3162
3162
$filename = $mboxfile;
3163
3163
open (OUT_MBOX, ">$mboxfile")
3164
or die "bts: writing to mbox file $mboxfile: $!\n";
3164
or die "$progname: writing to mbox file $mboxfile: $!\n";
3166
3166
print OUT_MBOX $response->content;
3167
3167
close OUT_MBOX;
3169
3169
return ($fh, $filename);
3171
die "bts: failed to download mbox.\n";
3171
die "$progname: failed to download mbox.\n";
3508
3508
my $cachefile=cachefile($thing,$thgopts);
3509
3509
my $mboxfile=mboxfile($thing);
3510
3510
if ($mboxmode and ! $mboxfile) {
3511
die "bts: you can only request a mailbox for a single bug report.\n";
3511
die "$progname: you can only request a mailbox for a single bug report.\n";
3514
3514
# Check that if we're requesting a tag, that it's a valid tag
3515
3515
if (($thing.$thgopts) =~ /(?:^|;)(?:tag|include|exclude)[:=]([^;]*)/) {
3516
3516
unless (exists $valid_tags{$1}) {
3517
die "bts: invalid tag requested: $1\nRecognised tag names are: " . join(" ", @valid_tags) . "\n";
3517
die "$progname: invalid tag requested: $1\nRecognised tag names are: " . join(" ", @valid_tags) . "\n";
3522
3522
if ($offlinemode) {
3523
3523
$livedownload = 0;
3524
3524
if (! $hascache) {
3525
die "bts: Sorry, you are in offline mode and have no cache.\nRun \"bts cache\" or \"bts show\" to create one.\n";
3525
die "$progname: Sorry, you are in offline mode and have no cache.\nRun \"bts cache\" or \"bts show\" to create one.\n";
3527
3527
elsif ((! $mboxmode and ! -r $cachefile) or
3528
3528
($mboxmode and ! -r $mboxfile)) {
3529
die "bts: Sorry, you are in offline mode and that is not cached.\nUse \"bts [--cache-mode=...] cache\" to update the cache.\n";
3529
die "$progname: Sorry, you are in offline mode and that is not cached.\nUse \"bts [--cache-mode=...] cache\" to update the cache.\n";
3531
3531
if ($mboxmode) {
3532
3532
runmailreader($mboxfile);
3539
3539
if (! $hascache) {
3540
3540
if (! -d dirname($cachedir)) {
3541
3541
unless (mkdir(dirname($cachedir))) {
3542
warn "bts: couldn't mkdir ".dirname($cachedir).": $!\n";
3542
warn "$progname: couldn't mkdir ".dirname($cachedir).": $!\n";
3546
3546
unless (mkdir($cachedir)) {
3547
warn "bts: couldn't mkdir $cachedir: $!\n";
3547
warn "$progname: couldn't mkdir $cachedir: $!\n";
3564
3564
# Use filehandle for security
3565
3565
open (OUT_LIVE, ">&", $fh)
3566
or die "bts: writing to temporary file: $!\n";
3566
or die "$progname: writing to temporary file: $!\n";
3567
3567
# Correct relative urls to point to the bts.
3568
3568
$live =~ s%\shref="(?:/cgi-bin/)?(\w+\.cgi)% href="$btscgiurl$1%g;
3569
3569
print OUT_LIVE $live;
3607
3607
my $oldcwd = getcwd;
3609
chdir($cachedir) || die "bts: chdir $cachedir: $!\n";
3609
chdir($cachedir) || die "$progname: chdir $cachedir: $!\n";
3611
3611
# remove the now-defunct live-download file
3612
3612
unlink "live_download.html";
3614
opendir DIR, '.' or die "bts: opendir $cachedir: $!\n";
3614
opendir DIR, '.' or die "$progname: opendir $cachedir: $!\n";
3615
3615
my @cachefiles = grep { ! /^\.\.?$/ } readdir(DIR);
3649
3649
# We now remove the oldfiles if they're automatically downloaded
3650
3650
tie (%timestamp, "Devscripts::DB_File_Lock", $timestampdb,
3651
3651
O_RDWR()|O_CREAT(), 0600, $DB_HASH, "write")
3652
or die "bts: couldn't open DB file $timestampdb for writing: $!\n"
3652
or die "$progname: couldn't open DB file $timestampdb for writing: $!\n"
3653
3653
if ! tied %timestamp;
3655
3655
my @unrecognised;
3668
3668
untie %timestamp;
3670
3670
if (! -e $prunestamp) {
3671
open PRUNESTAMP, ">$prunestamp" || die "bts: prune timestamp: $!\n";
3671
open PRUNESTAMP, ">$prunestamp" || die "$progname: prune timestamp: $!\n";
3672
3672
close PRUNESTAMP;
3674
chdir $oldcwd || die "bts: chdir $oldcwd: $!\n";
3674
chdir $oldcwd || die "$progname: chdir $oldcwd: $!\n";
3675
3675
utime time, time, $prunestamp;
3688
3688
sub runmailreader {
3689
3689
my $file = shift;
3690
3690
my $quotedfile;
3691
die "bts: could not read mbox file!\n" unless -r $file;
3691
die "$progname: could not read mbox file!\n" unless -r $file;
3693
3693
if ($file !~ /\'/) { $quotedfile = qq['$file']; }
3694
3694
elsif ($file !~ /[\"\\\$\'\!]/) { $quotedfile = qq["$file"]; }
3695
else { die "bts: could not figure out how to quote the mbox filename \"$file\"\n"; }
3695
else { die "$progname: could not figure out how to quote the mbox filename \"$file\"\n"; }
3697
3697
my $reader = $mailreader;
3698
3698
$reader =~ s/\%([%s])/$1 eq '%' ? '%' : $quotedfile/eg;
3720
3720
tie (%timestamp, "Devscripts::DB_File_Lock", $timestampdb,
3721
3721
O_RDONLY(), 0600, $DB_HASH, "read")
3722
or die "bts: couldn't open DB file $timestampdb for reading: $!\n";
3722
or die "$progname: couldn't open DB file $timestampdb for reading: $!\n";
3724
3724
($timestamp, $versionstamp) = split /;/, $timestamp{$thing.$thgopts}
3725
3725
if exists $timestamp{$thing.$thgopts};
3742
3742
tie (%timestamp, "Devscripts::DB_File_Lock", $timestampdb,
3743
3743
O_RDWR()|O_CREAT(), 0600, $DB_HASH, "write")
3744
or die "bts: couldn't open DB file $timestampdb for writing: $!\n";
3744
or die "$progname: couldn't open DB file $timestampdb for writing: $!\n";
3746
3746
$timestamp{$thing.$thgopts} = "$timestamp;$versionstamp";
3759
3759
tie (%timestamp, "Devscripts::DB_File_Lock", $timestampdb,
3760
3760
O_RDWR()|O_CREAT(), 0600, $DB_HASH, "write")
3761
or die "bts: couldn't open DB file $timestampdb for writing: $!\n";
3761
or die "$progname: couldn't open DB file $timestampdb for writing: $!\n";
3763
3763
delete $timestamp{$thing.$thgopts};
3883
3883
SUFFIX => ".mail",
3884
3884
DIR => File::Spec->tmpdir);
3885
3885
open(OUT_MAIL, ">$filename")
3886
or die "bts: writing to temporary file: $!\n";
3886
or die "$progname: writing to temporary file: $!\n";
3887
3887
print OUT_MAIL $message;
3888
3888
close OUT_MAIL;
3889
3889
system("sensible-editor $filename");
3890
3890
open(OUT_MAIL, "<$filename")
3891
or die "bts: reading from temporary file: $!\n";
3891
or die "$progname: reading from temporary file: $!\n";
3893
3893
while(<OUT_MAIL>) {
3894
3894
$message .= $_;