#!/usr/bin/python3
# -*- python -*-
# reportbug - Report a bug in the Debian distribution.
# Written by Chris Lawrence <lawrencc@debian.org>
# Copyright (C) 1999-2008 Chris Lawrence
# Copyright (C) 2008-2022 Sandro Tosi <morph@debian.org>
#
# This program is freely distributable per the following license:
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose and without fee is hereby granted,
# provided that the above copyright notice appears in all copies and that
# both that copyright notice and this permission notice appear in
# supporting documentation.
#
# I DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL I
# BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY
# DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
# WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
# ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
# SOFTWARE.
import sys
import os
import optparse
import re
import locale
import requests
import subprocess
import shlex
import email
import gettext
import textwrap
from contextlib import suppress
# for blogging of attachments file
from glob import glob
from reportbug import utils
from reportbug import (
VERSION,
VERSION_NUMBER,
COPYRIGHT,
LICENSE
)
from reportbug.utils import (
MODE_EXPERT, MODE_ADVANCED, MODE_NOVICE, MODE_STANDARD,
)
from reportbug.tempfiles import (
TempFile,
tempfile_prefix,
cleanup_temp_file,
)
from reportbug.exceptions import (
UINotImportable, UINotImplemented,
NoNetwork, NoPackage, NoBugs, NoReport, QuertBTSError,
)
from reportbug import mailer
from reportbug import submit
from reportbug import checkversions
from reportbug import debbugs
from reportbug import checkbuildd
import reportbug.ui.text_ui as ui
from reportbug.ui import (
AVAILABLE_UIS, UNAVAILABLE_UIS
)
with suppress(OSError):
gettext.install('reportbug')
DEFAULT_BTS = 'debian'
# Magic constant time
MIN_USER_ID = 250
quietly = False
# Cheat for now.
# ewrite() may put stuff on the status bar or in message boxes depending on UI
def ewrite(*args):
return quietly or ui.log_message(*args)
def efail(*args):
ui.display_failure(*args)
sys.exit(1)
def check_attachment_size(attachfile, maxsize):
"""Check if the attachment size is bigger than max allowed"""
statinfo = os.stat(attachfile)
attachsize = statinfo[6]
return attachsize >= maxsize
def include_file_in_report(message, message_filename,
attachment_filenames, package_name,
include_filename, charset, inline=False, draftpath=None):
""" Include a file in the report.
:parameters:
`message`
The current text of the message.
`message_filename`
The current message filename.
`attachment_filenames`
List of current attachment filenames.
`package_name`
Name of the package for this report.
`include_filename`
Full pathname of the file to be included.
`inline`
If True, include the message inline with the message
text. Otherwise, add the file path to the attachments.
:return value:
Tuple (`message`, `message_filename`, `attachments`) of
values as modified during the process of including the new
file.
"""
if inline:
try:
with open(include_filename, errors='backslashreplace') as fp:
message += '\n*** {}\n{}'.format(
include_filename,
fp.read())
fp, temp_filename = TempFile(
prefix=tempfile_prefix(package_name), dir=draftpath)
fp.write(message)
fp.close()
os.unlink(message_filename)
message_filename = temp_filename
except OSError as exc:
ui.display_failure('Unable to attach file %s\n%s\n',
include_filename, str(exc))
else:
attachment_filenames.append(include_filename)
return (message, message_filename, attachment_filenames)
def handle_editing(filename, dmessage, options, sendto, attachments, package,
severity, mode, is_followup, is_source, editor=None,
charset='utf-8', tags='', resumed=False):
if not editor:
editor = options.editor
editor = utils.which_editor(editor)
message = None
patch = False
skip_editing = False
# is this report just to be saved on a file ?
justsave = False
while True:
if not skip_editing:
(message, changed) = ui.spawn_editor(message or dmessage, filename,
editor, charset)
skip_editing = False
prompt = 'Submit this report on %s (e to edit)' % package
if options.kudos:
prompt = 'Send this message (e to edit)'
ewrite("Message will be sent to %s\n", sendto)
elif options.outfile:
ewrite("Report will be saved as %s\n", options.outfile)
else:
ewrite("Report will be sent to %s\n", sendto)
if attachments:
ewrite('Attachments:\n')
for name in attachments:
ewrite(' %s\n', name)
subject = re.search('^Subject: ', message, re.M | re.I)
if not subject:
ui.long_message('No subject found in message. Please edit again.\n')
menuopts = "Ynaceilmpqdt"
if not changed or not subject:
menuopts = "ynacEilmpqdt"
# cfr Debian BTS #293361
if package == 'wnpp':
for itp_line in debbugs.itp_template.rsplit('\n'):
# if the line is not empty and it's in the message the user wrote
if itp_line in message and itp_line != '':
ewrite("Wrong line: %s\n", itp_line)
menuopts = "Eq"
prompt = ("ERROR: you have composed a WNPP bug report with fields "
"unchanged from the template; this will NOT be submitted. "
"Please edit all fields so they contain correct values (e to edit)")
if options.outfile:
yesmessage = 'Save the report into %s .' % options.outfile
else:
yesmessage = 'Submit the bug report via email.'
x = ui.select_options(prompt, menuopts,
{'y': yesmessage,
'n': "Don't submit the bug report; instead, "
"save it in a temporary file (exits reportbug).",
'q': "Save it in a temporary file and quit.",
'a': "Attach a file.",
'd': "Detach an attachment file.",
'i': "Include a text file.",
'c': "Change editor and re-edit.",
'e': 'Re-edit the bug report.',
'l': 'Pipe the message through the pager.',
'p': 'Print message to stdout.',
't': 'Add tags.',
'm': "Choose a mailer to edit the report."})
if x in ('a', 'i'):
invalid = True
while invalid:
if x == 'i':
attachfile = ui.get_filename('Choose a text file to include: ')
else:
attachfile = ui.get_filename('Choose a file to attach: ')
if attachfile:
# expand vars & glob the input string
attachfile = os.path.expanduser(attachfile)
attachfglob = sorted(glob(attachfile), key=str.casefold)
# check if the globbing returns any result
if not attachfglob:
ui.display_failure("Can't find %s to include!\n", attachfile)
# loop over the globbed 'attachfile', you can specify wildcards now
for attachf in attachfglob:
if os.access(attachf, os.R_OK) and os.path.isfile(attachf):
if check_attachment_size(attachf, options.max_attachment_size):
ewrite('The attachment file %s size is bigger than the maximum of %d bytes: '
'reduce its size else the report cannot be sent\n' %
(attachf, options.max_attachment_size))
else:
invalid = False
inline = (x == 'i')
(message, filename, attachments) = include_file_in_report(
message, filename, attachments, package,
attachf, charset, inline=inline, draftpath=options.draftpath)
if not inline:
skip_editing = True
else:
ui.display_failure("Cannot include %s!\n", attachf)
else:
break
elif x == 'd':
skip_editing = True
if not attachments:
ewrite('No attachment file to detach.\n')
else:
detachprompt = 'Choose an attachment file to detach (an empty line will exit): '
myattachments = []
myattachments = [(x, '') for x in attachments]
filetodetach = ui.menu(detachprompt, myattachments,
'Select the file:', default='', empty_ok=True)
# only if selection is not empty and the file is in the attachment list
if filetodetach != '' and filetodetach in attachments:
attachments.remove(filetodetach)
ewrite('Attachment file "%s" successfully detached.\n\n', filetodetach)
else:
ewrite('Nothing to detach.\n\n')
elif x == 'c':
ed = ui.get_filename('Choose editor: ', default=options.editor)
if ed:
editor = ed
elif x == 'm':
skip_editing = True
mailers = [(x, '') for x in sorted(mailer.MUA.keys())
if mailer.mua_exists(x) and mailer.mua_can_run(x)]
if not mailers:
ui.display_failure('No mailers supported by reportbug found on this system.\n')
continue
mailprog = ui.menu('Choose a mailer for your report', mailers,
'Select mailer: ', default='', empty_ok=True)
if mailprog and mailprog != -1:
# get the MUA
mailprog = mailer.MUA.get(mailprog)
# if there are no attachments, directly go to the mailer
if not attachments:
options.mua = mailprog
break
# otherwise notify that they may be lost
if ui.yes_no(
'Editing the report might lose all attachments: are you sure you want to continue?',
'Yes, please',
'No, thanks',
True):
# if ok, go into the MUA
options.mua = mailprog
break
# else go back to the menu
else:
pass
elif x in ('n', 'q'):
justsave = True
break
elif x in ('l', 'p'):
skip_editing = True
if x == 'l':
pager = os.environ.get('PAGER', 'sensible-pager')
with os.popen(pager, 'w') as p:
p.write(message)
else:
sys.stdout.write(message)
elif x == 't':
newtaglist = []
skip_editing = True
ntags = debbugs.get_tags(severity, mode)
newtaglist = ui.select_multiple(
'Do any of the following apply to this report?', ntags,
'Please select tags: ')
if newtaglist:
tags_prefix = 'Control: tags -1 ' if is_followup else 'Tags: '
if tags:
oldtags = tags_prefix + tags
newtaglist += tags.split()
# suppress twins in the tag list
newtaglist = list(set(newtaglist))
newtags = tags_prefix + ' '.join(newtaglist)
else:
package_prefix = 'Source: ' if is_source else 'Package: '
oldtags = package_prefix + package + '\n'
newtags = oldtags + tags_prefix + ' '.join(newtaglist) + '\n'
if 'patch' in newtaglist:
patch = True
message = message.replace(oldtags, newtags)
with open(filename, 'w', errors='backslashreplace') as f:
f.write(message)
elif x == 'y':
if message == dmessage and not resumed:
x = ui.select_options(
'Report is unchanged. Edit this report or quit', 'Eqs',
{'q': "Don't submit the bug report; instead, save it "
"in a temporary file and quit.",
'e': 'Re-edit the bug report.',
's': 'Send report anyway.'})
if x == 'q':
justsave = True
break
elif x == 's':
ewrite('Sending unmodified report anyway...\n')
break
else:
break
return open(filename, errors='backslashreplace').read(), patch, justsave
def find_package_for(filename, notatty=False, pathonly=False):
ewrite("Finding package for '%s'...\n", filename)
(newfilename, packages) = utils.find_package_for(filename, pathonly)
if newfilename != filename:
filename = newfilename
ewrite("Resolved as '%s'.\n", filename)
if not packages:
ewrite("No packages match.\n")
return (filename, None)
elif len(packages) > 1:
packlist = list(packages.items())
packlist.sort()
if notatty:
print("Please re-run reportbug selecting one of these packages:")
for pkg, files in packlist:
print(" " + pkg)
sys.exit(1)
packs = []
for pkg, files in packlist:
if len(files) > 3:
files[3:] = ['...']
packs.append((pkg, ', '.join(files)))
package = ui.menu("Multiple packages match: ", packs, 'Select one '
'of these packages: ', any_ok=True)
# for urwid, when pressing 'Cancel' in the menu
if package == -1:
package = None
return (filename, package)
else:
package = list(packages.keys())[0]
filename = packages[package][0]
ewrite("Using package '%s'.\n", package)
return (filename, package)
def validate_package_name(package):
if not re.match(r'^(src:)?[a-z0-9][a-z0-9+.-]+$', package):
ui.long_message("%s is not a valid package name.", package)
package = None
return package
def get_other_package_name(others):
"""Displays the list of pseudo-packages and returns the one selected."""
result = ui.menu("Please enter the name of the package in which you "
"have found a problem, or choose one of these bug "
"categories:", others, "Enter a package: ", any_ok=True,
default='')
if result:
return result
else:
return None
def get_package_name(bts='debian', mode=MODE_EXPERT):
others = debbugs.SYSTEMS[bts].get('otherpkgs')
prompt = "Please enter the name of the package in which you have found " \
"a problem"
if others:
prompt += ", or type 'other' to report a more general problem."
else:
prompt += '.'
prompt += " If you don't know what package the bug is in, " \
"please contact debian-user@lists.debian.org for assistance."
options = []
pkglist = subprocess.getoutput('apt-cache pkgnames')
if pkglist:
options += pkglist.split()
if others:
options += list(others.keys())
package = None
while package is None:
package = ui.get_string(prompt, options, force_prompt=True)
if not package:
return
if others and package and package == 'other':
package = get_other_package_name(others)
if not package:
return
package = validate_package_name(package)
if package in ('kernel', 'linux-image'):
ui.long_message(
"Automatically selecting the package for the running kernel")
package = utils.get_running_kernel_pkg()
if mode < MODE_STANDARD:
if package == 'reportbug':
if not ui.yes_no('Is "reportbug" actually the package you are '
'having problems with?',
'Yes, I am actually experiencing a problem with '
'reportbug.',
'No, I really meant to file a bug report on '
'another package.'):
return get_package_name(bts, mode)
if mode < MODE_EXPERT:
if package in ('bugs.debian.org', 'debbugs'):
if ui.yes_no('Are you reporting a problem with this program (reportbug)?',
'Yes, this is actually a bug in reportbug.',
'No, this is really a problem in the bug tracking system itself.'):
package = 'reportbug'
if package in ('general', 'project', 'debian-general'):
ui.long_message(
"If you have a general problem, please do consider using "
'the available Debian support channels to narrow the problem '
'down. This will help us together to resolve the problem '
'quicker. See https://www.debian.org/support')
if not ui.yes_no(
"Are you sure this bug doesn't apply to a specific package?",
'Yes, this bug is truly general.',
'No, this is not really a general bug.', False):
return get_package_name(bts, mode)
if package == 'wnpp':
if not ui.yes_no(
'Are you sure you want to file a WNPP report?',
'Yes, I am a developer or know what I\'m doing.',
'No, I am not a developer and I don\'t know what wnpp means.',
False):
return get_package_name(bts, mode)
if package in ('ftp.debian.org', 'release.debian.org'):
if not ui.yes_no('Are you sure you want to file a bug on %s?' % (package),
'Yes, I am a developer or know what I\'m doing.',
'No, I am not a developer and I don\'t know what %s is.' % (package),
False):
return get_package_name(bts, mode)
if package in ('installation-report', 'upgrade-report'):
package += 's'
return package
def special_prompts(package, bts, ui, fromaddr, timeout, online, http_proxy):
prompts = debbugs.SYSTEMS[bts].get('specials')
if prompts:
pkgprompts = prompts.get(package)
if pkgprompts:
return pkgprompts(package, bts, ui, fromaddr, timeout, online, http_proxy)
return
def offer_configuration(options):
charset = locale.nl_langinfo(locale.CODESET)
# It would be nice if there were some canonical character set conversion
if charset.lower() == 'ansi_x3.4-1968':
charset = 'us-ascii'
ui.charset = charset
if not options.configure:
ui.long_message('Welcome to reportbug! Since it looks like this is '
'the first time you have used reportbug, we are '
'configuring its behavior. These settings will be '
'saved to the file "%s", which you will be free to '
'edit further.\n\n', utils.USERFILE)
mode = ui.menu('Please choose the default operating mode for reportbug.',
utils.MODES, 'Select mode: ', options.mode,
order=utils.MODELIST)
if options.configure or not options.interface:
# if there is only one UI available, the it's 'text', else ask
if len(AVAILABLE_UIS) == 1:
interface = 'text'
else:
interface = ui.menu(
'Please choose the default interface for reportbug.', AVAILABLE_UIS,
'Select interface: ', options.interface, order=['text'])
else:
interface = options.interface
online = ui.yes_no('Will reportbug often have direct '
'Internet access? (You should answer yes to this '
'question unless you know what you are doing and '
'plan to check whether duplicate reports have been '
'filed via some other channel.)',
'Yes, reportbug should assume it has access to the '
'network always.',
'No, I am only online occasionally to send and '
'receive mail.',
default=(not options.offline))
def_realname, def_email = utils.get_email()
try:
if options.realname:
realname = options.realname
else:
realname = def_realname
except UnicodeDecodeError:
realname = ''
realname = ui.get_string('What real name should be used for sending bug '
'reports?', default=realname, force_prompt=True)
realname = realname.replace('"', '\\"')
is_addr_ok = False
while not is_addr_ok:
from_addr = ui.get_string(
'Which of your email addresses should be used when sending bug '
'reports? (Note that this address will be visible in the bug tracking '
'system, so you may want to use a webmail address or another address '
'with good spam filtering capabilities.)',
default=(options.email or def_email), force_prompt=True)
is_addr_ok = utils.check_email_addr(from_addr)
if not is_addr_ok:
ewrite('Your email address is not valid; please try another one.\n')
stupidmode = not ui.yes_no(
'Do you have a "mail transport agent" (MTA) like Exim, Postfix or '
'SSMTP configured on this computer to send mail to the Internet?',
'Yes, I can run /usr/sbin/sendmail without horrible things happening. '
'If you can send email from this machine without setting an SMTP Host '
'in your mailer, you should choose this answer.',
'No, I need to use an SMTP Host or I don\'t know if I have an MTA.',
(not options.smtphost) if options.smtphost else False)
if stupidmode:
opts = []
if options.smtphost:
opts += [options.smtphost]
smtphost = ui.get_string(
'Please enter the name of your SMTP host. Usually it\'s called '
'something like "mail.example.org" or "smtp.example.org". '
'If you need to use a different port than default, use the '
'<host>:<port> alternative format.\n\n'
'Just press ENTER if you don\'t have one or don\'t know, and '
'so a Debian SMTP host will be used.',
options=opts, empty_ok=True, force_prompt=True)
if smtphost:
stupidmode = False
else:
smtphost = ''
if smtphost:
smtpuser = ui.get_string(
('If you need to use a user name to send email via "%s" on your '
'computer, please enter that user name. Just press ENTER if you '
'don\'t need a user name.' % smtphost), empty_ok=True, force_prompt=True)
else:
smtpuser = ''
if smtphost and not smtphost.endswith(':465'):
smtptls = ui.yes_no(
'Do you want to encrypt the SMTP connection with TLS (only '
'available if the SMTP host supports STARTTLS)?', 'Yes', 'No',
default=False)
else:
smtptls = False
http_proxy = ui.get_string(
'Please enter the name of your proxy server. It should only '
'use this parameter if you are behind a firewall. '
'The PROXY argument should be formatted as a valid HTTP URL,'
' including (if necessary) a port number;'
' for example, http://192.168.1.1:3128/. '
'Just press ENTER if you don\'t have one or don\'t know.',
empty_ok=True, force_prompt=True)
if os.path.exists(utils.USERFILE):
try:
os.rename(utils.USERFILE, utils.USERFILE + '~')
except OSError:
ui.display_failure('Unable to rename %s as %s~\n', utils.USERFILE,
utils.USERFILE)
try:
fd = os.open(utils.USERFILE, os.O_WRONLY | os.O_TRUNC | os.O_CREAT,
0o600)
except OSError:
efail('Unable to save %s; most likely, you do not have a '
'home directory. Please fix this before using '
'reportbug again.\n', utils.USERFILE)
fp = os.fdopen(fd, 'w', errors='backslashreplace')
print('# reportbug preferences file', file=fp)
print('# character encoding: %s' % charset, file=fp)
print('# Version of reportbug this preferences file was written by', file=fp)
print('reportbug_version "%s"' % VERSION_NUMBER, file=fp)
print('# default operating mode: one of:', end=' ', file=fp)
print(', '.join(utils.MODELIST), file=fp)
print('mode %s' % mode, file=fp)
print('# default user interface', file=fp)
print('ui %s' % interface, file=fp)
print('# offline disables querying information over the network', file=fp)
if not online:
print('offline', file=fp)
else:
print('#offline', file=fp)
print('# name and email setting (if non-default)', file=fp)
rn = 'realname "%s"'
em = 'email "%s"'
email_addy = (from_addr or options.email or def_email)
email_name = (realname or options.realname or def_realname)
if email_name != def_realname:
print(rn % email_name, file=fp)
else:
print('# ' + (rn % email_name), file=fp)
if email_addy != def_email:
print(em % email_addy, file=fp)
else:
print('# ' + (em % email_addy), file=fp)
uid = os.getuid()
if uid < MIN_USER_ID:
print('# Suppress user ID check for this user', file=fp)
print('no-check-uid', file=fp)
if smtphost:
print('# Send all outgoing mail via the following host', file=fp)
print('smtphost "%s"' % smtphost, file=fp)
if smtpuser:
print('smtpuser "%s"' % smtpuser, file=fp)
print('#smtppasswd "my password here"', file=fp)
else:
print('# If you need to enter a user name and password:', file=fp)
print('#smtpuser "my username here"', file=fp)
print('#smtppasswd "my password here"', file=fp)
if smtptls:
print('# Require STARTTLS for the SMTP host connection', file=fp)
print('smtptls', file=fp)
else:
print('# Enable this to use STARTTLS for the SMTP host connection', file=fp)
print('#smtptls', file=fp)
if http_proxy:
print('# Your proxy server address', file=fp)
print('http_proxy "%s"' % http_proxy, file=fp)
if stupidmode:
print('# Disable fallback mode by commenting out the following:', file=fp)
print('no-cc', file=fp)
print('list-cc-me', file=fp)
print('smtphost reportbug.debian.org', file=fp)
else:
print('# If nothing else works, remove the # at the beginning', file=fp)
print('# of the following three lines:', file=fp)
print('#no-cc', file=fp)
print('#list-cc-me', file=fp)
print('#smtphost reportbug.debian.org', file=fp)
print('# You can add other settings after this line. See', file=fp)
print('# /etc/reportbug.conf for a full listing of options.', file=fp)
fp.close()
if options.configure:
ui.final_message('Default preferences file written. To reconfigure, '
're-run reportbug with the "--configure" option.\n')
else:
ui.long_message('Default preferences file written. To reconfigure, '
're-run reportbug with the "--configure" option.\n')
def verify_option(option, opt, value, parser, *args):
heading, valid = args
if value == 'help':
ewrite('%s:\n %s\n' % (heading, '\n '.join(valid)))
sys.exit(1)
if opt in ['-u', '--interface', '--ui'] and value == 'gtk2':
value = 'gtk'
if value in valid:
setattr(parser.values, option.dest, value)
elif opt in ['-u', '--interface', '--ui'] and value in UNAVAILABLE_UIS:
ewrite('Cannot use UI %s because of "%s", defaulting to "text"\n' % (value, UNAVAILABLE_UIS[value]))
else:
ewrite('Ignored bogus setting for %s: %s\n' % (opt, value))
def verify_append_option(option, opt, value, parser, *args):
heading, valid = args
# special case --tag: in valid we pass a function reference
# as get_tags is dependent on the user mode, so we also have to convert
# the mode to the integer value expected... FIXME
if opt == '--tag' or opt == '-T':
valid = sorted(valid(mode=utils.MODELIST.index(parser.values.mode)).keys()) + ['none']
if value == 'help':
ewrite('%s:\n %s\n' % (heading, '\n '.join(valid)))
sys.exit(1)
elif value in valid:
try:
getattr(parser.values, option.dest).append(value)
except AttributeError:
setattr(parser.values, option.dest, [value])
else:
ewrite('Ignored bogus setting for %s: %s\n' % (opt, value))
def main():
global quietly, ui
try:
locale.setlocale(locale.LC_ALL, '')
except locale.Error as x:
print('*** Warning:', x, file=sys.stderr)
charset = locale.nl_langinfo(locale.CODESET)
# It would be nice if there were some canonical character set conversion
if charset.lower() == 'ansi_x3.4-1968':
charset = 'us-ascii'
defaults = dict(sendto="submit", mode="novice", mta="/usr/sbin/sendmail",
check_available=True, query_src=True, debconf=True,
editor='', offline=False, verify=True, check_uid=True,
testmode=False, attachments=[], keyid='', body=None,
resume_saved=None,
bodyfile=None, smtptls=False, smtpuser='', smtppasswd='',
paranoid=False, mbox_reader_cmd=None)
# Convention: consider `option.foo' names read-only; they always contain
# the original value as determined by the cascade of command-line options
# and configuration files. When we need to adjust a value, we first say
# "foo = options.foo" and then refer to just `foo'.
args = utils.parse_config_files()
for option, arg in list(args.items()):
if option in utils.CONFIG_ARGS:
defaults[option] = arg
else:
sys.stderr.write('Warning: untranslated token "%s"\n' % option)
parser = optparse.OptionParser(
usage='%prog [options] <package | filename>', version=VERSION)
parser.set_defaults(**defaults)
parser.add_option('-c', '--no-config-files', action="store_true",
dest='noconf', help='do not include conffiles in report')
parser.add_option('-C', '--class', action='callback', type='string',
callback=verify_option, dest="klass", metavar='CLASS',
callback_args=('Permitted report classes',
debbugs.CLASSLIST),
help='specify report class for GNATS BTSes')
parser.add_option('-d', '--debug', action='store_true', default=False,
dest='debugmode', help='send report only to yourself')
parser.add_option('--test', action="store_true", default=False,
dest="testmode",
help="operate in test mode (maintainer use only)")
parser.add_option('-e', '--editor', dest='editor',
help='specify an editor for your report')
parser.add_option('-f', '--filename', dest='searchfor',
help='report the bug against the package containing the specified file')
parser.add_option('--from-buildd', dest='buildd_format',
help='parse information from buildd format: $source_$version')
parser.add_option('--path', dest='pathonly', action="store_true",
default=False, help='only search the path with -f')
parser.add_option('-g', '--gnupg', '--gpg', action='store_const',
dest='sign', const='gpg',
help='sign report with GNU Privacy Guard (GnuPG/gpg)')
parser.add_option('-G', '--gnus', action='store_const', dest='mua',
const=mailer.MUA['gnus'],
help='send the report using Gnus')
parser.add_option('--pgp', action='store_const', dest='sign',
const='pgp',
help='sign report with Pretty Good Privacy (PGP)')
parser.add_option('-K', '--keyid', type="string", dest="keyid",
help="key ID to use for PGP/GnuPG signatures")
parser.add_option('-H', '--header', action='append', dest='headers',
help='add a custom RFC2822 header to your report')
parser.add_option('-P', '--pseudo-header', action='append', dest='pseudos',
help='add a custom pseudo-header to your report')
parser.add_option('--license', action='store_true', default=False,
help='show copyright and license information')
parser.add_option('-m', '--maintonly', action='store_const',
dest='sendto', const='maintonly',
help='send the report to the maintainer only')
parser.add_option('-M', '--mutt', action='store_const', dest='mua',
const=mailer.MUA['mutt'],
help='send the report using mutt')
parser.add_option('--mirror', action='append', help='add a BTS mirror',
dest='mirrors')
parser.add_option('-n', '--mh', '--nmh', action='store_const', dest='mua',
help='send the report using mh/nmh',
const=mailer.MUA['mh'])
parser.add_option('-N', '--bugnumber', action='store_true',
dest='bugnumber', help='specify a bug number to look for')
parser.add_option('--mua', dest='mua',
help='send the report using the specified mail user agent')
parser.add_option('--mta', dest='mta', help='send the report using the '
'specified mail transport agent')
parser.add_option('--list-cc', action='append', dest='listcc',
help='send a copy to the specified address')
parser.add_option('--list-cc-me', action='store_true', dest='listccme',
help='send a copy to your detected email address')
parser.add_option('-p', '--print', action='store_true', dest='printonly',
help='output the report to standard output only')
parser.add_option('--report-quiet', action='store_const', dest='sendto',
const='quiet', help='file report without any mail to '
'the maintainer or tracking lists')
parser.add_option('-q', '--quiet', action='store_true', dest='quietly',
help='reduce the verbosity of the output', default=False)
parser.add_option('-s', '--subject', help='the subject for your report')
parser.add_option('-x', '--no-cc', dest='nocc', action='store_true',
help='do not send a copy of the report to yourself')
parser.add_option('-z', '--no-compress', dest='nocompress',
action='store_true', help='do not strip blank lines '
'and comments from config files')
parser.add_option('-o', '--output', dest='outfile', help='output the report'
' to the specified file (both mail headers and body)')
parser.add_option('-O', '--offline', help='disable all external queries',
action='store_true')
parser.add_option('-i', '--include', action='append',
help='include the specified file in the report')
parser.add_option('-A', '--attach', action='append', dest='attachments',
help='attach the specified file to the report')
parser.add_option('-b', '--no-query-bts', action='store_true',
dest='dontquery', help='do not query the BTS for reports')
parser.add_option('--query-bts', action='store_false', dest='dontquery',
help='query the BTS for reports')
parser.add_option('-T', '--tag', action='callback', dest='tags',
callback=verify_append_option, type='string',
callback_args=('Permitted tags',
debbugs.get_tags),
help='add the specified tag to the report')
parser.add_option('--http_proxy', '--proxy', help='use this proxy for '
'HTTP accesses')
parser.add_option('--email', help='specify originating email address')
parser.add_option('--realname', help='specify real name for your report')
parser.add_option('--smtphost', help='specify SMTP server for mailing')
parser.add_option('--tls', help='use TLS to talk to SMTP servers',
dest="smtptls", action='store_true')
parser.add_option('--source', '--src', dest='source', default=False,
help='report the bug against the source package ',
action='store_true')
parser.add_option('--smtpuser', help='username to use for SMTP')
parser.add_option('--smtppasswd', help='password to use for SMTP')
parser.add_option('--replyto', '--reply-to', help='specify Reply-To '
'address for your report')
parser.add_option('--query-source', action='store_true', dest='query_src',
help='query on source packages, not binary packages')
parser.add_option('--no-query-source', action='store_false',
dest='query_src', help='query on binary packages only')
parser.add_option('--security-team', action='store_true', dest='secteam', default=None,
help='send the report only to the security team, if tag=security')
parser.add_option('--no-security-team', action='store_false', default=None,
dest='secteam', help='do not send the report only to the security team, if tag=security')
parser.add_option('--debconf', action='store_true',
help='include debconf settings in your report')
parser.add_option('--no-debconf', action='store_false', dest='debconf',
help='exclude debconf settings from your report')
parser.add_option('-j', '--justification', help='include justification '
'for the severity of your report')
parser.add_option('-V', '--package-version', dest='pkgversion',
help='specify the version number for the package')
parser.add_option('-u', '--interface', '--ui', action='callback',
callback=verify_option, type='string', dest='interface',
callback_args=('Valid user interfaces',
list(AVAILABLE_UIS.keys())),
help='choose which user interface to use')
parser.add_option('-Q', '--query-only', action='store_true',
dest='queryonly', help='only query the BTS')
parser.add_option('-t', '--type', action='callback', dest='type',
callback=verify_option, type='string',
callback_args=('Valid types of report',
('gnats', 'debbugs')),
help='choose the type of report to file')
parser.add_option('-B', '--bts', action='callback', dest='bts',
callback=verify_option, type='string',
callback_args=('Valid bug tracking systems',
list(debbugs.SYSTEMS.keys())),
help='choose BTS to file the report with')
parser.add_option('-S', '--severity', action='callback',
callback=verify_option, type='string', dest='severity',
callback_args=('Valid severities', debbugs.SEVLIST),
help='identify the severity of the report')
parser.add_option('--template', action='store_true',
help='output a template report only')
parser.add_option('--configure', action='store_true',
help='reconfigure reportbug for this user')
parser.add_option('--check-available', action='store_true',
help='check for new releases on various sites')
parser.add_option('--no-check-available', action='store_false',
dest='check_available', help='do not check for new '
'releases')
parser.add_option('--mode', action='callback', help='choose the operating '
'mode for reportbug', callback=verify_option,
type='string', dest='mode',
callback_args=('Permitted operating modes',
list(utils.MODES.keys())))
parser.add_option('-v', '--verify', action='store_true', help='verify '
'integrity of installed package using debsums')
parser.add_option('--no-verify', action='store_false', dest='verify',
help='do not verify package installation')
parser.add_option('-k', '--kudos', action='store_true', default=False,
help='send appreciative email to the maintainer, rather '
'than filing a bug report')
parser.add_option('--body', dest="body", type="string",
help="specify the body for the report as a string")
parser.add_option('--body-file', '--bodyfile', dest="bodyfile",
type="string",
help="use the specified file as the body of the report")
parser.add_option('-r', '--resume-saved', dest="resume_saved",
type="string",
help="resume report from previously saved temporary file")
parser.add_option('-I', '--no-check-installed', action='store_false',
default=True, dest='querydpkg',
help='don\'t check whether the package is installed')
parser.add_option('--check-installed', action='store_true',
dest='querydpkg', help='check whether the specified '
'package is installed when filing a report (default)')
parser.add_option('--paranoid', action='store_true', dest='paranoid',
help='show contents of message before sending')
parser.add_option('--no-paranoid', action='store_false', dest='paranoid',
help='don\'t show contents of message before sending '
'(default)')
parser.add_option('--no-bug-script', dest="bugscript", default=True,
action='store_false',
help='don\'t execute the bug script (if present)')
parser.add_option('--draftpath', dest="draftpath",
help='Save the draft in this directory')
parser.add_option('--timeout', type="int", dest='timeout', default=60,
help='Specify the network timeout, in seconds [default: %default]')
parser.add_option('--no-cc-menu', dest="ccmenu", default=True,
action='store_false',
help='don\'t show additional CC menu')
parser.add_option('--no-tags-menu', dest="tagsmenu", default=True,
action='store_false',
help='don\'t show tags menu')
parser.add_option('--mbox-reader-cmd', dest='mbox_reader_cmd',
help="Specify the program to open the reports mbox.")
parser.add_option('--max-attachment-size', type="int", dest='max_attachment_size',
help="Specify the maximum size in byte for an attachment [default: 10485760].")
parser.add_option('--latest-first', action='store_true', dest='latest_first', default=False,
help='Order bugs to show the latest first')
parser.add_option('--envelope-from', dest='envelopefrom',
help='Specify the Envelope From (Return-path) address used to send the bug report')
parser.add_option('--archive', dest='archived', action="store_true",
default=False, help='also show archived bugs')
(options, args) = parser.parse_args()
# if not set in config file or on cli, then set 10M as default
if not options.max_attachment_size:
options.max_attachment_size = 10485760
# check if attachment files exist, else exiting
# all are checked, and it doesn't exit at the first missing
if options.email:
if not utils.check_email_addr(options.email):
ewrite('Your email address is not valid; exiting.\n')
sys.exit(1)
if options.attachments:
# support glob
globbed_attachments = []
any_missing = False
for attachment in options.attachments:
globres = sorted(glob(attachment), key=str.casefold)
if globres:
globbed_attachments.extend(globres)
else:
ewrite(f"The attachment file '{attachment}' could not be found.\n")
any_missing = True
options.attachments = globbed_attachments
for attachment in options.attachments:
if check_attachment_size(attachment, options.max_attachment_size):
ewrite('The attachment file %s size is bigger than the maximum of %d bytes: reduce '
'its size else the report cannot be sent.\n' % (attachment, options.max_attachment_size))
any_missing = True
if any_missing:
ewrite("The above file(s) can't be attached; exiting.\n")
sys.exit(1)
if options.keyid and not options.sign:
ewrite('Option -K/--keyid requires --gpg or --pgp sign option set, which currently is not; exiting.\n')
sys.exit(1)
if options.draftpath:
options.draftpath = os.path.expanduser(options.draftpath)
if not os.path.exists(options.draftpath):
ewrite("The directory %s does not exist; exiting.\n" % options.draftpath)
sys.exit(1)
if options.mua and not options.template:
if not mailer.mua_is_supported(options.mua):
ewrite("Specified mail user agent is not supported; exiting.\n")
sys.exit(1)
if not mailer.mua_exists(options.mua):
ewrite("Selected mail user agent cannot be found; exiting.\n")
sys.exit(1)
if not mailer.mua_can_run(options.mua):
ewrite("Selected mail user agent cannot be run without graphical display; exiting.\n")
sys.exit(1)
# we want options.mua to be a mailer.Mua instance
options.mua = mailer.MUA.get(options.mua, options.mua)
if options.http_proxy:
os.environ['http_proxy'] = options.http_proxy
os.environ['https_proxy'] = options.http_proxy
# try to import the specified UI, but only if template
# is not set (it's useful only in 'text' UI).
if options.interface and not options.template:
interface = options.interface
iface = '%(interface)s_ui' % vars()
try:
lib_package = __import__('reportbug.ui', fromlist=[iface])
newui = getattr(lib_package, iface)
except UINotImportable as msg:
ui.long_message('*** Unable to import %s interface: %s '
'Falling back to text interface.\n',
interface, msg)
ewrite('\n')
if newui.initialize():
ui = newui
submit.ui = ui
else:
ui.long_message('*** Unable to initialize %s interface. '
'Falling back to text interface.\n',
interface)
# Add INTERFACE as an environment variable to access it from the
# script gathering the special information for reportbug, when
# a new bug should be filed against it.
os.environ['INTERFACE'] = interface
if not ui.can_input():
defaults.update({'dontquery': True, 'notatty': True,
'printonly': True})
# force to report the bug against the source package if --from-buildd
if options.buildd_format:
options.source = True
iface = UI(options, args)
if not hasattr(ui, 'run_interface'):
return iface.user_interface()
return ui.run_interface(iface.user_interface)
class UI(object):
def __init__(self, options, args):
self.options = options
self.args = args
def user_interface(self):
body = ''
filename = None
notatty = not ui.ISATTY
charset = locale.nl_langinfo(locale.CODESET)
# It would be nice if there were some canonical character set conversion
if charset.lower() == 'ansi_x3.4-1968':
charset = 'us-ascii'
# Allow the UI to know what charset we're using
ui.charset = charset
if self.options.configure:
offer_configuration(self.options)
sys.exit(0)
elif self.options.license:
print(COPYRIGHT)
print()
print(LICENSE)
sys.exit(0)
# These option values may get adjusted below, so give them a variable name.
sendto = self.options.sendto
check_available = self.options.check_available
dontquery = self.options.dontquery
headers = self.options.headers or []
pseudos = self.options.pseudos or []
mua = self.options.mua
pkgversion = self.options.pkgversion
global quietly
quietly = self.options.quietly
severity = self.options.severity
smtphost = self.options.smtphost
subject = self.options.subject
bts = self.options.bts or 'debian'
sysinfo = debbugs.SYSTEMS[bts]
rtype = self.options.type or sysinfo.get('type')
attachments = self.options.attachments
pgp_addr = self.options.keyid
bugnumber = self.options.bugnumber
bugscript = self.options.bugscript
pkgavail = maintainer = origin = src_name = state = debsumsoutput = ''
depends = []
recommends = []
suggests = []
conffiles = []
reportinfo = None
issource = installed = usedavail = False
status = None
# if user specified a bug number on the command-line, don't query BTS
if bugnumber:
dontquery = True
if self.options.body:
body = textwrap.fill(self.options.body)
elif self.options.bodyfile:
try:
if check_attachment_size(self.options.bodyfile, self.options.max_attachment_size):
print('Body file %s size bigger than the maximum of %d bytes: '
'reduce its size else the report cannot be sent' % (
self.options.bodyfile, self.options.max_attachment_size))
raise Exception
with open(self.options.bodyfile, errors='backslashreplace') as bf:
body = bf.read()
except Exception:
efail('Unable to read body from file %s.\n', self.options.bodyfile)
elif self.options.resume_saved:
try:
with open(self.options.resume_saved, 'rb') as fb:
msg = email.message_from_binary_file(fb, policy=email.policy.default)
body = msg.get_body().get_content()
subject = subject or msg.get('subject')
if sendto == "submit":
sendto = msg.get('to')
dontquery = True
check_available = False
bugscript = False
severity = 'normal'
except Exception:
efail('Unable to read message from file %s.\n',
self.options.resume_saved)
if body and not body.endswith('\n'):
body += '\n'
if self.options.queryonly:
check_available = False
if self.options.offline:
check_available = False
dontquery = True
if self.options.tags:
taglist = self.options.tags
if 'none' in taglist:
taglist = []
else:
taglist = []
if self.options.testmode:
self.options.debugmode = True
self.options.tags = ['none']
check_available = False
dontquery = True
severity = 'normal'
subject = 'testing'
taglist = []
interactive = True
if self.options.template:
check_available = interactive = False
dontquery = quietly = notatty = True
mua = smtphost = None
severity = severity or 'wishlist'
subject = subject or 'none'
taglist = taglist or []
if self.options.outfile or self.options.printonly:
mua = smtphost = None
if smtphost and smtphost.lower() in ('master.debian.org', 'bugs.debian.org'):
ui.long_message(
"*** Warning: %s is no longer an appropriate smtphost setting for reportbug: "
"it has been superseded by reportbug.debian.org and this one is forced as "
"smtphost; please update your .reportbugrc file.\n",
smtphost.lower())
smtphost = 'reportbug.debian.org'
if any(hd.startswith('X-Debbugs-CC: ') for hd in headers):
ui.long_message(
"*** Warning: You are trying to set an X-Debbugs-CC header. "
"This is possibly an old default setting from your ~/.reportbugrc. "
"In that case you may want to re-run 'reportbug --configure', or edit "
"your configuration file to use the 'list-cc-me' command (without recipient "
"address) instead. If you used the -H option on the command line, please "
"see the '--list-cc' option. "
"Reportbug cannot handle custom headers reliably with its MUA support, it is "
"therefore recommended to use pseudoheaders instead where possible.\n")
if utils.first_run():
if not self.args and not self.options.searchfor:
offer_configuration(self.options)
# due to the multithreaded gtk UI we cannot just
# call main() again, but need to re-execute reportbug
os.execv(__file__, sys.argv)
sys.exit(0)
else:
ewrite('Warning: no reportbug configuration found. Proceeding in %s mode.\n' % self.options.mode)
mode = utils.MODELIST.index(self.options.mode)
# Disable signatures when in printonly or mua mode
# (since they'll be bogus anyway)
sign = self.options.sign
if (self.options.mua or self.options.printonly) and sign:
sign = ''
if self.options.mua:
ewrite('The signature option is ignored when using an MUA.\n')
elif self.options.printonly:
ewrite('The signature option is ignored when producing a template.\n')
uid = os.getuid()
if uid < MIN_USER_ID:
if notatty and not uid:
ewrite("reportbug will not run as root non-interactively.\n")
sys.exit(1)
if not uid or self.options.check_uid:
if not uid:
message = "Running 'reportbug' as root is probably insecure!"
else:
message = "Running 'reportbug' as an administrative user " \
"is probably not a good idea!"
message += ' Continue?'
if not ui.yes_no(message, 'Continue with reportbug.', 'Exit.',
False):
ewrite("reportbug stopped.\n")
sys.exit(1)
if (utils.first_run() and not self.args and not self.options.searchfor):
offer_configuration(self.options)
ewrite('To report a bug, please rerun reportbug.\n')
sys.exit(0)
if self.options.mta and not os.path.exists(self.options.mta) and not (
self.options.mua or self.options.template or self.options.printonly
or self.options.smtphost or self.options.outfile):
ewrite(f"The MTA {self.options.mta} is not available; exiting.\n")
ewrite("Please run 'reportbug --configure' or specify a submission method on the command line.\n")
sys.exit(1)
foundfile = None
package = None
if self.options.resume_saved:
# dummy values for package and pkgversion; we use ones known
# to exist to avoid any further questions. The real ones
# will be taken from the resumed report anyway.
package = "reportbug"
pkgversion = VERSION_NUMBER
elif not len(self.args) and not self.options.searchfor and not notatty and not self.options.buildd_format:
package = get_package_name(bts, mode)
elif self.options.buildd_format:
# retrieve package name and version from the input string
package, self.options.pkgversion = self.options.buildd_format.split('_')
# TODO: fix it when refactoring
# if not done as of below, it will ask for version when the package
# is not available on the local system (try a dummy one, like foo_12-3)
pkgversion = self.options.pkgversion
elif len(self.args) > 1:
ewrite("Please report one bug at a time.\n")
ewrite("[Did you forget to put all switches before the "
"package name?]\n")
sys.exit(1)
elif self.options.searchfor:
(foundfile, package) = find_package_for(self.options.searchfor, notatty,
self.options.pathonly)
elif len(self.args):
package = self.args[0]
if package and package.startswith('/'):
(foundfile, package) = find_package_for(package, notatty)
elif package and self.options.source:
# convert it to the source package if we are reporting for src
package = utils.get_source_name(package)
elif package.lower() in ('general', 'project', 'debian-general') and mode < MODE_EXPERT:
ui.long_message(
"If you have a general problem, please do consider using "
'the available Debian support channels to narrow the problem '
'down. This will help us together to resolve the problem '
'quicker. See https://www.debian.org/support')
if not ui.yes_no(
"Are you sure this bug doesn't apply to a specific package?",
'Yes, this bug is truly general.',
'No, this is not really a general bug.', False):
package = get_package_name(bts, mode)
if package and package.startswith('src:'):
package = package[4:]
issource = True
others = debbugs.SYSTEMS[bts].get('otherpkgs')
if package == 'other' and others:
package = get_other_package_name(others)
if package in ('kernel', 'linux-image'):
ui.long_message(
"Automatically selecting the package for the running kernel")
package = utils.get_running_kernel_pkg()
if package in ('installation-report', 'upgrade-report') and mode < MODE_EXPERT:
package += 's'
if not package:
efail("No package specified or we were unable to find it in the apt"
" cache; stopping.\n")
tfprefix = tempfile_prefix(package)
if self.options.interface == 'text':
ewrite('*** Welcome to reportbug. Use ? for help at prompts. ***\n')
# we show this for the 2 "textual" UIs
if self.options.interface in ('text', 'urwid'):
ewrite('Note: bug reports are publicly archived (including the email address of the submitter).\n')
try:
_ = 'hello'
except LookupError:
ui.display_failure(
'Unable to use specified character set "%s"; you probably need '
'either cjkcodecs (for users of Asian locales) or iconvcodec '
'installed.\nFalling back to ASCII encoding.\n', charset)
charset = 'us-ascii'
else:
ewrite("Detected character set: %s\n"
"Please change your locale if this is incorrect.\n\n", charset)
fromaddr = utils.get_user_id(self.options.email, self.options.realname, charset)
if not utils.check_email_addr(email.utils.parseaddr(fromaddr)[1]):
efail("Unable to identify a valid from address, please run 'reportbug --configure'\n")
ewrite("Using '%s' as your from address.\n",
str(email.header.make_header(email.header.decode_header(fromaddr))))
if '@localhost' in fromaddr:
ewrite("Please quit and run 'reportbug --configure' if this is not correct.\n")
if self.options.debugmode:
sendto = fromaddr
edname = utils.which_editor(self.options.editor)
baseedname = os.path.basename(edname)
if baseedname == 'sensible-editor':
edname = utils.realpath('/usr/bin/editor')
if not notatty and 'vi' in baseedname and mode < MODE_STANDARD and 'EDITOR' not in os.environ:
if not ui.yes_no('You appear to be using the "vi" editor, which is '
'not suited for new users. You probably want to '
'change this setting by using "update-alternatives '
'--config editor" as root. (You can bypass this '
'message in the future by using reportbug in '
'"standard" mode or higher.) '
'Do you want to continue?',
'Continue filing this report.',
'Stop reportbug to change editors.', False):
ewrite('Exiting per user request.\n')
sys.exit(1)
incfiles = ""
if self.options.include:
for f in self.options.include:
if os.path.exists(f):
with open(f, errors='backslashreplace') as fp:
incfiles += '\n*** {}\n{}'.format(f, fp.read())
else:
ewrite("Can't find %s to include!\n", f)
sys.exit(1)
incfiles += '\n'
if self.options.source:
issource = True
exinfo = None
# If user specified a bug number on the command line
try:
if bugnumber:
reportre = re.compile(r'^#?(\d+)$')
match = reportre.match(package)
if match:
report = int(match.group(1))
exinfo = ui.show_report(report, 'debian', self.options.mirrors,
self.options.http_proxy,
self.options.timeout,
queryonly=self.options.queryonly,
title=VERSION,
archived=self.options.archived,
mbox_reader_cmd=self.options.mbox_reader_cmd)
# When asking to re-display the bugs list, None is returned
# given we're in the part of code that's executed when the
# user pass a bug number on the cli, so we'll exit
if exinfo is None:
raise NoReport
else:
package = exinfo.package or exinfo.source
subject = subject or exinfo.subject
if package == 'src:linux':
package = utils.get_running_kernel_pkg()
elif package.startswith('src:'):
package = package[4:]
issource = True
else:
efail("The report bug number provided seems to not exist.\n")
except NoBugs:
efail('No such bug report.\n')
except NoReport:
efail('Exiting.\n')
isvirtual = (package in list(sysinfo.get('otherpkgs', {}).keys())
and package not in sysinfo.get('nonvirtual', []))
if issource and not pkgversion:
# package is already ok here, just need the version
pkgversion = utils.get_source_version(package)
if not pkgversion and self.options.querydpkg and \
sysinfo.get('query-dpkg', True) and \
package not in list(debbugs.SYSTEMS[bts].get('otherpkgs').keys()):
ewrite("Getting status for %s...\n", package)
status = utils.get_package_status(package)
pkgavail, installed = status[1], status[6]
# Packages that only exist to do weird dependency things
deppkgs = sysinfo.get('deppkgs')
if pkgavail and deppkgs:
if installed and package in deppkgs:
depends = status[2]
if depends:
newdepends = []
for x in depends:
newdepends.extend(x)
depends = newdepends
if len(depends) == 1:
if mode < MODE_ADVANCED:
ewrite('Dependency package "%s" corresponds to '
'actual package "%s".\n', package, depends[0])
package = depends[0]
else:
opts = [(x, (utils.get_package_status(x)[11] or 'not installed'))
for x in depends]
if mode >= MODE_ADVANCED:
opts += [(package,
status[11] + ' (dependency package)')]
package = ui.menu('%s is a dependency package. '
'Which of the following '
'packages is the bug in?' % package,
opts,
'Select one of these packages: ')
ewrite("Getting status for %s...\n", package)
status = utils.get_package_status(package)
pkgavail, installed = status[1], status[6]
if not pkgavail and not isvirtual:
# Look for a matching source package
packages = utils.get_source_package(package)
if len(packages) > 0:
if not notatty and len(packages) > 1:
package = ui.menu(
'Which of the following packages is the bug in?',
packages, empty_ok=True,
prompt='Select one of these packages: ')
else:
package = packages[0][0]
if not package:
efail("No package specified; stopping.\n")
if package.startswith('src:'):
package = package[4:]
issource = True
pkgversion = utils.get_source_version(package)
else:
ewrite("Getting status for %s...\n", package)
status = utils.get_package_status(package)
pkgavail, installed = status[1], status[6]
else:
ewrite('No matching source or binary packages.\n')
if (not installed and not isvirtual and not issource) and not notatty:
packages = utils.packages_providing(package)
tmp = pack = None
if not packages:
if ui.yes_no('A package named "%s" does not appear to be installed; do '
'you want to search for a similar-looking filename in '
'an installed package?' % package,
'Look for a file with a similar filename.',
'Continue filing with this package name.', True):
pkgavail = False
else:
pack = package
packages = [(package, '')]
ewrite("Getting available info for %s...\n", package)
status = utils.get_package_status(package, avail=True)
check_available = False
usedavail = True
if not packages and not pkgavail and not pack:
(tmp, pack) = find_package_for(package, notatty)
if pack:
status = None
if not ui.yes_no("A package named '%s' does not appear to be installed "
"on your system; however, '%s' contains a file named "
"'%s'. Do you want to file your report on the "
"package reportbug found?" % (package, pack, tmp),
'Yes, use the package specified.',
'No, give up the search.'):
efail("Package not installed; stopping.\n")
if not status and pack:
foundfile, package = tmp, pack
ewrite("Getting status for %s...\n", package)
status = utils.get_package_status(package)
elif not packages:
if not ui.yes_no(
'This package does not appear to be installed; continue '
'with this report?', 'Ignore this problem and continue.',
'Exit without filing a report.', False):
efail("Package not installed; stopping.\n")
elif (len(packages) == 1) and (packages[0][0] != package):
if not ui.yes_no(
'This package does not appear to be installed: {}.\n '
'Do you want to file the report on package {} instead?'
.format(package, packages[0][0]),
'Yes, use the other package.',
'Exit without filing a report.',
True):
efail("Package not installed; stopping.\n")
else:
package = packages[0][0]
ewrite("Getting status for %s...\n", package)
status = utils.get_package_status(package)
elif len(packages) > 1:
packages.sort()
package = ui.menu('Which of the following installed packages '
'is the bug in?', packages,
'Select one of these packages: ',
empty_ok=True)
if not package:
efail("No package specified; stopping.\n")
else:
ewrite("Getting status for %s...\n", package)
status = utils.get_package_status(package)
elif not pkgavail and not notatty and not isvirtual and not issource:
if not ui.yes_no(
'This package does not appear to exist; continue?',
'Ignore this problem and continue.',
'Exit without filing a report.', False):
efail("Package does not exist; stopping.\n")
sys.exit(1)
# we can use status only if it's not a source pkg
if not issource:
(pkgversion, pkgavail, depends, recommends, conffiles, maintainer,
installed, origin, vendor, reportinfo, priority, desc, src_name,
fulldesc, state, suggests, section) = status
buginfo = '/usr/share/bug/' + package
bugexec = submitas = submitto = presubj = None
reportwith = []
supplemental = []
if self.options.resume_saved:
pass
elif os.path.isfile(buginfo) and os.access(buginfo, os.X_OK):
bugexec = buginfo
elif os.path.isdir(buginfo):
if os.path.isfile(buginfo + '/script') and os.access(buginfo + '/script', os.X_OK):
bugexec = buginfo + '/script'
if os.path.isfile(buginfo + '/presubj'):
presubj = buginfo + '/presubj'
if os.path.isfile(buginfo + '/control'):
submitas, submitto, reportwith, supplemental = \
utils.parse_bug_control_file(buginfo + '/control')
elif os.path.isfile('/usr/share/bug/default/' + package) \
and os.access('/usr/share/bug/default/' + package, os.X_OK):
bugexec = '/usr/share/bug/default/' + package
elif os.path.isdir('/usr/share/bug/default/' + package):
buginfo = '/usr/share/bug/default/' + package
if os.path.isfile(buginfo + '/script') and os.access(buginfo + '/script',
os.X_OK):
bugexec = buginfo + '/script'
if os.path.isfile(buginfo + '/presubj'):
presubj = buginfo + '/presubj'
if os.path.isfile(buginfo + '/control'):
submitas, submitto, reportwith, supplemental = \
utils.parse_bug_control_file(buginfo + '/control')
if submitas and (submitas not in reportwith):
reportwith += [submitas]
if reportwith:
# Remove current package from report-with list
reportwith = [x for x in reportwith if x != package]
if (pkgavail and self.options.verify and os.path.exists('/usr/bin/debsums')
and not self.options.kudos and state == 'installed'):
ewrite('Verifying package integrity...\n')
fullpackagename = package
try:
fullpackagename = utils.get_command_output(
"dpkg-query -W -f='${binary:Package}\n' %s 2>/dev/null" % shlex.quote(package)).split()[0]
except IndexError:
pass
rc, output = subprocess.getstatusoutput('/usr/bin/debsums --ignore-permissions -s '
+ shlex.quote(fullpackagename))
debsumsoutput = output
if rc and not notatty:
if not ui.yes_no(f'There may be a problem with your installation of {package};\n'
'the following problems were detected by debsums:\n'
f'{output}\nDo you still want to file a report?',
'Ignore this problem and continue. This may be '
'appropriate if you have fixed the package manually already. '
'This problem may also result from the use of localepurge.',
'Exit without filing a report.', False, nowrap=True):
efail("Package integrity check failed; stopping.\n")
if not pkgversion or usedavail or (not pkgavail and not issource):
if not bugnumber and not (isvirtual or notatty) and not self.options.resume_saved:
pkgversion = ui.get_string('Please enter the version of the '
'package this report applies to '
'(blank OK)', empty_ok=True, force_prompt=True)
elif (check_available and not (self.options.kudos or notatty or self.options.offline)
and state == 'installed' and bts == 'debian'):
arch = utils.get_arch()
check_more = (mode > MODE_STANDARD)
if check_more:
ewrite('Checking for newer versions at madison'
' and https://ftp-master.debian.org/new.html\n')
else:
ewrite('Checking for newer versions at madison...\n')
(avail, toonew) = checkversions.check_available(
package, pkgversion, timeout=self.options.timeout,
check_incoming=check_more, check_newqueue=check_more,
http_proxy=self.options.http_proxy, arch=arch)
if toonew:
if not ui.yes_no('\nYour version of %s (%s) is newer than that in Debian!\n'
'Do you still want to file a report?' % (package, pkgversion),
'Ignore this problem and continue. This may be '
'appropriate if you know this bug is present in older '
'releases of the package, or you\'re running a mixed '
'stable/testing installation.',
'Exit without filing a report.', False):
efail("Newer released version; stopping.\n")
if avail:
availtext = ''
availlist = list(avail.keys())
availlist.sort()
for rel in availlist:
availtext += ' %s: %s\n' % (rel, avail[rel])
if not ui.yes_no(('\nYour version (%s) of %s appears to be out of date.\nThe '
'following newer release(s) are available in the Debian '
'archive:\n' % (pkgversion, package)) + availtext
+ 'Please try to verify if the bug you are about to report is '
+ 'already addressed by these releases. Do you still want to file a report?',
'Ignore this problem and continue. This may be '
'appropriate if you know this bug is still present in more '
'recent releases of the package.',
'Exit without filing a report.', False, nowrap=True):
efail("Newer released version; stopping.\n")
bts = DEFAULT_BTS
if self.options.bts:
bts = self.options.bts
ewrite("Will send report to %s (per request).\n",
debbugs.SYSTEMS[bts].get('name', bts))
elif origin:
if origin.lower() == bts:
ewrite("Package originates from %s.\n", vendor or origin)
reportinfo = None
elif origin.lower() in list(debbugs.SYSTEMS.keys()):
ewrite("Package originates from %s; overriding your system "
"selection.\n", vendor or origin)
bts = origin.lower()
sysinfo = debbugs.SYSTEMS[bts]
elif reportinfo:
ewrite("Unknown origin %s; will send to %s.\n", origin,
reportinfo[1])
rtype, submitto = reportinfo
elif submitto:
ewrite("Unknown origin %s; will send to %s.\n", origin, submitto)
else:
ewrite("Unknown origin %s; will send to %s.\n", origin, bts)
elif reportinfo:
rtype, submitto = reportinfo
ewrite("Will use %s protocol talking to %s.\n", rtype, submitto)
dontquery = True
else:
lsbr = subprocess.getoutput('lsb_release -si 2>/dev/null')
if lsbr:
distro = lsbr.strip().lower()
if distro in debbugs.SYSTEMS:
bts = distro
ewrite("Will send report to %s (per lsb_release).\n",
debbugs.SYSTEMS[bts].get('name', bts))
if rtype == 'mailto':
rtype = 'debbugs'
dontquery = True
special = False
if not body and not subject and not notatty:
res = special_prompts(package, bts, ui, fromaddr,
self.options.timeout,
not self.options.offline
and (check_available or not dontquery),
self.options.http_proxy)
if res:
(subject, severity, h, ph, body, query) = res
headers += h
pseudos += ph
if not query:
dontquery = True
special = True
if not (dontquery or notatty or self.options.kudos):
pkg, src = package, issource
if self.options.query_src and not issource and not isvirtual:
pkg = [pkg]
if src_name:
pkg += ['src:' + src_name]
elif not package.startswith('src:'):
pkg += ['src:' + package]
if submitas and submitas not in pkg:
pkg += [submitas]
try:
exinfo = ui.handle_bts_query(pkg, bts, self.options.timeout,
self.options.mirrors,
self.options.http_proxy,
source=src,
queryonly=self.options.queryonly,
archived=self.options.archived,
version=pkgversion,
mbox_reader_cmd=self.options.mbox_reader_cmd,
latest_first=self.options.latest_first)
except UINotImplemented:
exinfo = None
except NoNetwork:
sys.exit(1)
except NoPackage:
if not self.options.queryonly and maintainer and ui.yes_no(
'There is no record of this package in the bug tracking '
'system.\nSend report directly to maintainer?',
'Send the report to the maintainer (%s).' % maintainer,
'Send the report to the BTS anyway.'):
rtype = 'debbugs'
sendto = maintainer
except NoBugs:
ewrite('No bug reports found.\n')
except NoReport:
if self.options.queryonly:
ewrite('Exiting at user request.\n')
else:
ewrite('Nothing new to report; exiting.\n')
return
except QuertBTSError as q:
if not ui.yes_no(
'Error retrieving information on existing bug reports from the BTS. '
'The following error was detected:\n'
+ str(q)
+ '\nDo you still want to file a report?',
'Keep going', 'Abort', False, nowrap=True):
return
if self.options.queryonly and not exinfo:
return
ccaddr = os.environ.get('MAILCC')
if self.options.nocc:
bccaddr = os.environ.get('MAILBCC')
else:
bccaddr = os.environ.get('MAILBCC', fromaddr)
if maintainer:
mstr = "Maintainer for %s is '%s'.\n" % (package, maintainer)
ewrite(mstr)
if 'qa.debian.org' in maintainer:
ui.long_message(
"This package seems to be currently \"orphaned\"; it also seems you're a bit "
"interested in this package, since you're reporting a bug against it, so you "
"might consider being involved in the package maintenance in Debian and/or "
"adopting it. Please be aware that your report may not be resolved for a while, "
"because the package seems to lack an active maintainer, but please GO ON and "
"REPORT the bug, if there is one.\n"
"\n"
"For more details, please see: https://www.debian.org/devel/wnpp/ "
)
if self.options.kudos and not self.options.debugmode:
sendto = '%s@packages.debian.org' % package
depinfo = ""
# Grab dependency list, removing version conditions.
if (depends or recommends or suggests) and not self.options.kudos:
ewrite("Looking up dependencies of %s...\n", package)
depinfo = (utils.get_dependency_info(package, depends)
+ utils.get_dependency_info(package, recommends, "recommends")
+ utils.get_dependency_info(package, suggests, "suggests"))
if reportwith and not self.options.kudos:
# retrieve information for the packages listed in 'report-with' bug
# control file field
for extrapackage in reportwith:
ewrite("Getting status for related package %s...\n", extrapackage)
extrastatus = utils.get_package_status(extrapackage)
# depends
if extrastatus[2]:
extradepends = [x for x in extrastatus[2] if package not in x]
ewrite("Looking up 'depends' of related package %s...\n", extrapackage)
depinfo += utils.get_dependency_info(extrapackage, extradepends)
# recommends
if extrastatus[3]:
extrarecommends = [x for x in extrastatus[3] if package not in x]
ewrite("Looking up 'recommends' of related package %s...\n", extrapackage)
depinfo += utils.get_dependency_info(extrapackage, extrarecommends, "recommends")
# suggests
if extrastatus[15]:
extrasuggests = [x for x in extrastatus[15] if package not in x]
ewrite("Looking up 'suggests' of related package %s...\n", extrapackage)
depinfo += utils.get_dependency_info(extrapackage, extrasuggests, "suggests")
if supplemental and not self.options.kudos:
ewrite("Looking up status of additional packages...\n")
depinfo += utils.get_dependency_info(
package, [[x] for x in supplemental], rel='is related to')
confinfo = []
if conffiles and not self.options.kudos:
ewrite("Getting changed configuration files...\n")
confinfo, changed = utils.get_changed_config_files(
conffiles, self.options.nocompress)
if self.options.noconf and changed:
for f in changed:
confinfo[f] = 'changed [not included]'
elif changed and not notatty:
while 1:
x = ui.select_options(
"*** WARNING: The following configuration files have been "
"modified:\n" + "\n".join(changed)
+ "\nSend modified configuration files", 'Ynd',
{'y': 'Send your modified configuration files.',
'n': "Don't send modified configuration files.",
'd': 'Display modified configuration files (exit with "q").'})
if x == 'n':
for f in changed:
confinfo[f] = 'changed [not included]'
break
elif x == 'd':
PAGER = os.environ.get('PAGER', '/usr/bin/sensible-pager')
ui.system(PAGER + ' ' + ' '.join(changed))
else:
break
conftext = ''
if confinfo:
conftext = '\n-- Configuration Files:\n'
files = list(confinfo.keys())
files.sort()
for f in files:
conftext = conftext + '%s %s\n' % (f, confinfo[f])
if (self.options.debconf and os.path.exists('/usr/bin/debconf-show')
and not self.options.kudos and installed):
showpkgs = package
if reportwith:
showpkgs += ' ' + ' '.join(reportwith)
r = subprocess.run(
'DEBCONF_SYSTEMRC=1 DEBCONF_NOWARNINGS=yes '
'/usr/bin/debconf-show %s' % showpkgs,
shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
(status, output) = r.returncode, r.stdout.decode(errors='backslashreplace').rstrip()
if status:
conftext += '\n-- debconf-show failed\n'
elif output:
if (notatty or ui.yes_no("*** The following debconf settings were detected:\n"
+ output + "\nInclude these settings in your report?",
'Send your debconf settings.',
"Don't send your debconf settings.", nowrap=True)):
conftext += '\n-- debconf information:\n%s\n' % output
else:
conftext += '\n-- debconf information excluded\n'
else:
conftext += '\n-- no debconf information\n'
ewrite('\n')
prompted = False
if interactive and not (self.options.kudos or exinfo) and presubj:
with open(presubj, errors='backslashreplace') as f:
ui.display_report(f.read() + '\n', presubj=True)
if self.options.kudos:
subject = subject or ('Thanks for packaging %s!' % package)
elif exinfo:
if special:
body = ''
prompted = True
subject_ok = bool(self.options.subject)
while not subject_ok:
subject = ui.get_string(
'Please provide a subject for your response.', default="Re: %s" % exinfo.subject, force_prompt=True)
if subject:
subject_ok = True
else:
ewrite("Providing a subject is mandatory.\n")
# Check to make sure the bug still exists to avoid auto-reopens
if subject and pkgversion:
if not ui.yes_no('Are you able to confirm that this bug still exists in version %s '
'of this package?' % pkgversion,
'Yes, it does.',
'No, it doesn\'t (or I don\'t know).',
default=False):
pkgversion = None
elif not subject and not notatty:
prompted = True
subject_ok = False
while not subject_ok:
subject = ui.get_string(
'Briefly describe the problem (max. 100 characters '
'allowed). This will be the bug email subject, so keep the '
'summary as concise as possible, for example: "fails to '
'send email" or "does not start with -q option specified" '
'(enter Ctrl+c to exit reportbug without reporting a bug).',
force_prompt=True)
if subject:
subject_ok = True
else:
ewrite("Providing a subject is mandatory.\n")
if len(subject) > 100 and prompted and mode < MODE_EXPERT:
subject = ui.get_string(
'Your description is a bit long; please enter a shorter subject. '
'(An empty response will retain the existing subject.)',
empty_ok=True, force_prompt=True) or subject
if package != 'wnpp' and mode < MODE_EXPERT:
if foundfile:
subject = foundfile + ": " + subject
ewrite("Rewriting subject to '%s'\n", subject)
elif not re.match(r"\S+:\s", subject) and not subject.startswith(package):
subject = package + ": " + subject
ewrite("Rewriting subject to '%s'\n", subject)
listcc = self.options.listcc
if not listcc:
listcc = []
if not listcc and mode > MODE_STANDARD and rtype == 'debbugs' and not self.options.testmode and not self.options.template and self.options.ccmenu:
listcc += ui.get_multiline(
'Enter any additional addresses this report should be sent to; press ENTER after each address.')
if self.options.listccme:
detected_addr = self.options.email or utils.get_email()[1]
if not detected_addr:
efail("list-cc-me option specified but email address not detected")
listcc += [detected_addr]
# If the bug is reported against a package with a version that possibly
# indicates a security update add the security or LTS team to CC
# after user confirmation
if pkgversion and package and not self.options.offline and mode > MODE_NOVICE and utils.is_security_update(package, pkgversion):
if ui.yes_no('Do you want to report a regression because of a security update?',
'Yes, please inform the LTS and security teams.',
'No or I am not sure.', True):
distnumber = re.search(r'[+~]deb(\d+)u\d+', pkgversion).group(1)
support = 'none'
email_address = 'none'
try:
r = requests.get('https://security-tracker.debian.org/tracker/distributions.json', timeout=self.options.timeout)
data = r.json()
for key, value in data.items():
if distnumber == value['major-version']:
support = value['support']
email_address = value['contact']
break
if support != 'none' and utils.check_email_addr(email_address):
listcc += [email_address]
else:
raise
except requests.exceptions.RequestException:
ewrite('Unable to connect to security-tracker.debian.org.\n'
'Please try again later or contact the LTS or security team via email directly.\n')
except Exception: # catch-all
ewrite('No support team contact address could be identified.\n')
if severity and rtype:
severity = debbugs.convert_severity(severity, rtype)
klass = self.options.klass
if not notatty and not (exinfo or self.options.kudos):
if not severity:
if rtype == 'gnats':
severities = debbugs.SEVERITIES_gnats
default = 'non-critical'
else:
severities = debbugs.SEVERITIES
if mode < MODE_STANDARD:
ewrite("Removing release critical severities, since running in \'%s\' mode.\n" % utils.MODELIST[mode])
for sev in ['critical', 'grave', 'serious', 'does-not-build']:
del severities[sev]
if isvirtual:
for sev in ['critical', 'does-not-build']:
if sev in severities:
del severities[sev]
default = 'normal'
while not severity or severity not in debbugs.SEVLIST:
severity = ui.menu("How would you rate the severity of this "
"problem or report?", severities,
'Please select a severity level: ',
default=default, order=debbugs.SEVLIST[::-1])
# urwid has a cancel and a quit button that return < 0
if isinstance(severity, int) and severity < 0:
sys.exit()
if rtype == 'gnats':
# Class of report
klass = ui.menu("What sort of problem are you reporting?",
debbugs.CLASSES, 'Please select a class: ',
default='sw-bug', order=debbugs.CLASSLIST)
severity = severity or 'normal'
justification = self.options.justification
if rtype == 'debbugs' and package != 'wnpp' and mode < MODE_EXPERT:
if severity in ('critical', 'grave'):
justification = ui.menu(
'You are reporting a ' + severity + ' bug; which of the '
'following criteria does it meet?',
debbugs.JUSTIFICATIONS[severity],
'Please select the impact of the bug: ', default='unknown')
elif severity == 'serious':
justification = ui.get_string(
'You are reporting a serious bug; which section of the '
'Debian Policy Manual contains the "must" or "required" '
'directive that it violates (E.g., "1.2.3")? '
'Just type "unknown" if you are not sure (that would '
'downgrade severity to normal).', force_prompt=True)
if re.match(r'[0-9]+\.[0-9.]+', justification):
justification = 'Policy ' + justification
elif not justification:
justification = 'unknown'
if justification == 'unknown':
justification = ''
severity = 'normal'
ewrite('Severity downgraded to "normal".\n')
if severity == 'does-not-build':
if pkgversion and not src_name:
src_name = package
if src_name and check_available and not notatty:
ewrite('Checking buildd.debian.org for past builds of %s...\n',
src_name)
built = checkbuildd.check_built(src_name,
http_proxy=self.options.http_proxy,
timeout=self.options.timeout)
severity = 'serious'
justification = 'fails to build from source'
# special-case only if it was built in the past
if built:
justification += ' (but built successfully in the past)'
else:
severity = 'serious'
justification = 'fails to build from source'
if not notatty:
# special-case only if it was built in the past
if ui.yes_no(
'Has this package successfully been built for this '
'architecture in the past (you can look this up at '
'buildd.debian.org)?',
'Yes, this is a recently-introduced problem.',
'No, it has always been this way.'):
justification += ' (but built successfully in the past)'
HOMEDIR = os.environ.get('HOME', '/')
if (
rtype == 'debbugs'
and not self.options.tags
and not (notatty or self.options.kudos or exinfo)
and package not in ('wnpp', 'ftp.debian.org', 'release.debian.org')
and not self.options.resume_saved
and mode > MODE_NOVICE
and self.options.tagsmenu
):
tags = debbugs.get_tags(severity, mode)
taglist = ui.select_multiple(
'Do any of the following apply to this report?', tags,
'Please select tags: ')
if taglist is None:
# We've pressed cancel or quit in urwid
sys.exit()
patch = ('patch' in taglist)
if justification and 'security' not in taglist and 'security' in \
justification:
ewrite('Adding security tag to this report.\n')
taglist += ['security']
if justification and 'ftbfs' not in taglist and 'fails to build from source' in justification:
ewrite('Adding ftbfs tag to this report.\n')
taglist += ['ftbfs']
if taglist:
tags = ' '.join(taglist)
else:
tags = ''
if 'security' in taglist:
if self.options.secteam or (self.options.secteam is None and ui.yes_no(
'Are you reporting an undisclosed vulnerability? If so, in order '
'to responsibly disclose the issue, it should not be sent to the public BTS '
'right now, but instead to the private Security Team mailing list.',
'Yes, it is an undisclosed vulnerability, send this report to the '
'private Security Team mailing list and not to the BTS.',
'No, it is already a publicly disclosed vulnerability, send this report to the BTS.', False)):
sendto = 'team@security.debian.org'
# Execute bug script
if bugscript and bugexec and not self.options.kudos:
# add a warning, since it can take a while, 587952
ewrite("Gathering additional data, this may take a while...\n")
handler = '/usr/share/reportbug/handle_bugscript'
# we get the return code of the script, headers and pseudo- set
# by the script, and last the text output of the script
(rc, bugscript_hdrs, bugscript_pseudo, text, bugscript_attachments) = \
utils.exec_and_parse_bugscript(handler, bugexec, ui.system)
if rc and not notatty:
if not ui.yes_no('The package bug script %s exited with an error status (return '
'code = %s). Do you still want to file a report?' % (bugexec, rc),
'Ignore this problem and continue.',
'Exit without filing a report.', False, nowrap=True):
efail("Package bug script failed; stopping.\n")
# add bugscript headers only if present
if bugscript_hdrs:
headers.extend(bugscript_hdrs.split('\n'))
if bugscript_pseudo:
pseudos.append(bugscript_pseudo.strip())
if bugscript_attachments:
attachments += bugscript_attachments
addinfo = None
if not self.options.noconf:
addinfo = "\n-- Package-specific info:\n" + text
if addinfo and incfiles:
incfiles = addinfo + "\n" + incfiles
elif addinfo:
incfiles = addinfo
if bts == 'debian' and 'security' in taglist and sendto != 'team@security.debian.org':
ewrite('Will send a CC of this report to the Debian Security Team.\n')
listcc += ['Debian Security Team <team@security.debian.org>']
listcc = [cc.strip() for cc in listcc if cc.strip()]
if listcc:
pseudos.append('X-Debbugs-Cc: ' + ', '.join(listcc))
# Prepare bug report
if self.options.kudos:
message = '\n\n'
if not mua:
SIGFILE = os.path.join(HOMEDIR, '.signature')
with suppress(OSError):
with open(SIGFILE, errors='backslashreplace') as sf:
message = "\n\n-- \n" + sf.read()
else:
p = submitas or package
# multiarch: remove arch qualifier only if we're not reporting
# against the src package
if not p.startswith('src:'):
p = p.split(':')[0]
if self.options.resume_saved:
message = body
else:
message = utils.generate_blank_report(
p, pkgversion, severity, justification,
depinfo, conftext, foundfile, incfiles, bts, exinfo, rtype,
klass, subject, tags, body, mode, pseudos, debsumsoutput,
issource=issource, options=self.options)
# Substitute server email address
if submitto and '@' not in sendto:
if '@' in submitto:
sendto = submitto
else:
if exinfo:
if sendto != 'submit':
sendto = '%d-%s' % (exinfo.bug_num, sendto)
else:
sendto = str(exinfo.bug_num)
sendto = sendto + '@' + submitto
elif '@' not in sendto:
if exinfo:
if sendto != 'submit':
sendto = '%d-%s' % (exinfo.bug_num, sendto)
else:
sendto = str(exinfo.bug_num)
try:
sendto = sysinfo['email'] % sendto
except TypeError:
sendto = sysinfo['email']
sendto = email.utils.formataddr(
(sysinfo['name'] + ' Bug Tracking System', sendto))
mailing = not (mua or self.options.printonly or self.options.template)
message = "Subject: %s\n%s" % (subject, message)
justsave = False
if mailing:
fh, filename = TempFile(prefix=tfprefix, dir=self.options.draftpath)
fh.write(message)
fh.close()
oldmua = mua or self.options.mua
if not self.options.body and not self.options.bodyfile:
message, haspatch, justsave = handle_editing(
filename, message, self.options, sendto, attachments,
package, severity, mode, bool(exinfo), issource,
charset=charset, tags=tags,
resumed=bool(self.options.resume_saved))
if haspatch:
patch = True
if not oldmua and self.options.mua:
mua = self.options.mua
if mua:
mailing = False
elif not sendto:
print(message, end=' ')
cleanup_temp_file(filename)
return
cleanup_temp_file(filename)
if not mua and patch and not attachments and not notatty:
while True:
patchfile = ui.get_filename(
'What is the filename of the patch (if none, or you have '
'already included it, just press ENTER)?',
force_prompt=True)
if patchfile:
attachfile = os.path.expanduser(patchfile)
# loop over the glob of 'attachfile', we support glob now
for attachf in sorted(glob(attachfile), key=str.casefold):
if os.path.exists(attachfile):
attachments.append(attachfile)
else:
ewrite('%s not found!', attachfile)
else:
break
# Pass both headers and pseudo-headers (passed on command-line, f.e.)
body, headers, pseudoheaders = utils.cleanup_msg(message, headers, pseudos, rtype)
if sign:
ewrite('Passing message to %s for signature...\n', sign)
oldbody = body
body = submit.sign_message(body, fromaddr, package, pgp_addr, sign, self.options.draftpath)
if not body:
ewrite('Signature failed; sending message unsigned.\n')
body = oldbody
if pseudoheaders:
body = '\n'.join(pseudoheaders) + '\n\n' + body
# Strip the body of useless whitespace at the end, then put a final
# newline in the message. See #234963.
body = body.rstrip('\n') + '\n'
if not pseudoheaders and not justsave:
ui.display_failure('Invalid bug report message: No pseudoheaders found.\n'
'Your message will not be submitted, but stored in a temporary file instead.\n')
justsave = True
if justsave:
if not self.options.outfile:
fh, outputfile = TempFile(prefix=tfprefix,
dir=self.options.draftpath)
fh.close()
mua = mailing = False
# fake sending the report, it actually saves it in a tempfile
# but with all the email headers and stuff
submit.send_report(
body, attachments, mua, fromaddr, sendto, ccaddr, bccaddr,
headers, package, charset, mailing, sysinfo, rtype, exinfo,
self.options.replyto,
outfile=self.options.outfile or outputfile, mta=None,
smtphost=None)
else:
submit.send_report(
body, attachments, mua, fromaddr, sendto, ccaddr, bccaddr,
headers, package, charset, mailing, sysinfo, rtype, exinfo,
self.options.replyto, self.options.printonly,
self.options.template, self.options.outfile, self.options.mta,
self.options.kudos, self.options.smtptls, smtphost,
self.options.smtpuser, self.options.smtppasswd,
self.options.paranoid, self.options.draftpath,
self.options.envelopefrom)
ui.final_message('Thank you for using reportbug\n')
return
if __name__ == '__main__':
try:
main()
except KeyboardInterrupt:
ewrite("\nreportbug: exiting due to user interrupt.\n")
except debbugs.Error as x:
ewrite('error accessing BTS: %s\n' % x)
|