#!/usr/bin/python
#
# reportbug - Report a bug in the Debian distribution.
#   Written by Chris Lawrence <lawrencc@debian.org>
#   Copyright (C) 1999-2002 Chris Lawrence
#
# 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.
#
# Version ##VERSION##; see changelog for revision history

MUA = "reportbug ##VERSION##"
COPYRIGHT = MUA + ':\n  Copyright (C) 1999-2002 Chris Lawrence <lawrencc@debian.org>'

import sys, string, getopt, re, os, tempfile, pwd, time, locale, commands
import rfc822, smtplib, reportbug, cStringIO, socket, debianbts

import reportbug_ui_text
ui = reportbug_ui_text

quietly = 0

# Cheat for now.
# ewrite() may put stuff on the status bar or in message boxes depending on UI
def ewrite(*args):
    return quietly or apply(ui.log_message, args)
# Python 2.x idiom:
#   return quietly or ui.log_message(*args)

# Lame message when we store a report as a temp file.
def stopmsg(filename):
    ui.long_message('reportbug stopped; your incomplete report is stored as '
                    '"%s".\n'
                    'This file may be located in a temporary directory; if '
                    'so, it might disappear without any further notice.\n',
                    filename)

# Safe open, prevents filename races in shared tmp dirs
# Based on python-1.5.2/Lib/tempfile.py
def open_write_safe(filename, mode='w+b', bufsize=-1):
    fd = os.open(filename, os.O_WRONLY|os.O_CREAT|os.O_EXCL, 0600)
    try:
        return os.fdopen(fd, mode, bufsize)
    except:
        os.close(fd)
        raise

# Obscene hack :)
def system(cmdline):
    try:
        x = os.getcwd()
    except OSError:
        os.chdir('/')
    os.system(cmdline)

def show_report(text, use_pager=1):
    if not use_pager:
        ewrite(text)
        return
        
    fd = os.popen('sensible-pager', 'w')
    fd.write(text)
    fd.close()

def spawn_editor(message, filename, default_editor=None):
    if not default_editor:
        editor = os.environ.get('VISUAL', os.environ.get('EDITOR', '/usr/bin/sensible-editor'))
    else:
        editor = default_editor

    edname = os.path.basename(string.split(editor)[0])
    
    # Move the cursor for lazy buggers like me; add your editor here...
    lineno = 1
    fp = open(filename)
    for line in fp.readlines():
        lineno = lineno+1
        if line == '\n': break

    opts = ''
    if lineno > 1:
        if edname in ('vi', 'nvi', 'vim', 'elvis'):
            opts = '-c :%d' % lineno
        elif edname in ('emacs', 'emacs19', 'emacs20', 'xemacs', 'xemacs20',
                        'xemacs21', 'elvis-tiny', 'gnuclient', 'ee',
                        'pico', 'nano'):
            opts = '+%d' % lineno
        elif edname in ('jed', 'xjed'):
            opts = '-g %d' % lineno

    if '&' in editor:
        ewrite("Spawning %s in background; please press Enter when done "
               "editing.\n", edname)
    else:
        ewrite("Spawning %s...\n", edname)

    result = system("%s %s '%s'" % (editor, opts, filename))

    if result:
        ewrite('Warning: possible error exit from %s: %d\n', edname, result)

    if not os.path.exists(filename):
        ewrite('Bug report file %s removed!', filename)
        sys.exit(1)

    if '&' in editor: return (None, 1)

    newmessage = open(filename).read()

    if newmessage == message:
        ewrite('No changes were made in the editor.\n')

    return (newmessage, newmessage != message)

def find_package_for(filename, notatty=0):
    ewrite("Finding package for %s\n", filename)
    (newfilename, packages) = reportbug.find_package_for(filename)
    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:
        if notatty:
            print "Please rerun reportbug selecting one of these packages:"
            for package in packlist:
                print "  "+package
            sys.exit(1)

        packlist = packages.items()
        packlist.sort()
        packs = []
        for pkg, files in packlist:
            if len(files) > 3:
                files[3:] = ['...']
            packs.append( (pkg, string.join(files, ', ')) )

        package = ui.menu("Multiple packages match: ", packs, 'Select one '
                          'of these packages: ', any_ok=1)
        return (filename, package)
    else:
        package = packages.keys()[0]
        ewrite("Using package %s\n", package)
        return (filename, package)

def get_package_name(bts='debian'):
    others = debianbts.SYSTEMS[bts]['otherpkgs']
    if others:
        package = 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=1)
    else:
        package = ui.get_string("Please enter the name of the package in "
                                "which you have found a problem:\n> ")

    if package in ('bugs.debian.org', 'debbugs'):
        yorn = ui.select_options(
            'Are you reporting a problem with this program (reportbug)',
            'Yn', {'y': 'Yes, this is actually a bug in reportbug.',
                   'n': 'No, this is really a problem in the BTS itself.'})
        if yorn == 'y':
            package = 'reportbug'

    if package in ('general', 'project', 'debian-general', 'base'):
        yorn = ui.select_options("Are you sure this bug doesn't apply to a "
                                 "specific package?", "yN",
                                 {'y': 'Yes, this bug is truly general.',
                                  'n': 'No, this is not really a general '
                                  'bug.'})
        if yorn == 'n':
            return get_package_name(bts)
        
    return package

def special_prompts(package, bts, ui):
    prompts = debianbts.SYSTEMS[bts].get('specials')
    if not prompts: return

    pkgprompts = prompts.get(package)
    if not pkgprompts: return

    return pkgprompts(package, bts, ui)

USAGE = ("reportbug - Report a bug to a debbugs bug tracking system.\n\n"
         "Usage: reportbug [options] <package>\n"
         "Supported options (see man page for long and long-only forms):\n"
         "  -a: Use af instead of an editor for filing the report.\n"
         "  -b: Don't query the BTS for existing bug reports.\n"
         "  -B: Specify an alternate debbugs BTS. *\n"
         "  -c: Don't include config files in bug reports.\n"
         "  -C: Class of bug report (for --type=gnats only).\n"
         "  -d: Send bug report to the reportbug maintainer, not the BTS.\n"
         "  -f: Report a bug in the specified file, looking up the package.\n"
         "  -g: Sign the bug report with GnuPG.\n"
         "  -h: Display this help message.\n"
         "  -H: Add a header to the outgoing email.\n"
         "  -i: Include specified file in bug report.\n"
         "  -j: Include justification for a serious+ bug.\n"
         "  -l: Enable LDAP access to BTSes that support it.\n"
         "  -L: Add X-Debbugs-CC to specified list.\n"
         "  -m: Send email to the maintainer only.\n"
         "  -M: Use mutt instead of an editor for filing the report.\n"
         "  -n: Use nmh/mh instead of an editor for filing the report.\n"
         "  -o: Output bug report to a file.\n"
         "  -p: Don't send any email; write bug template to standard output.\n"
         "  -P: Sign the bug report with PGP.\n"
         "  -q: Run quietly; suppress diagnostic output.\n"
         "  -Q: Query BTS only; don't submit any bugs.\n"
         "  -s: Specify the summary of the report on the command line.\n"
         "  -S: Specify the severity of the bug. *\n"
         "  -t: Type of report to be filed. * (default: debbugs)\n"
         "  -T: Tags to add to report.\n"
         "  -v: Show the version number of this program.\n"
         "  -V: Specify the version of the package the bug is in.\n"
         "  -x: Don't send a copy of the report to yourself.\n"
         "  -z: Don't compress the configuration files.\n"
         "\nOptions marked * take the word 'help' to list allowed options."
         )

def main():
    global quietly
    global ui
    
    sendto = 'submit'
    searchfor = severity = subject = outfile = sign = mua = origin = ''
    http_proxy = realname = email = replyto = smtphost = type = klass = ''
    force_bts = interface = tags = justification = pkgversion = body = ''
    package = ''
    headers = []
    include = []
    mirrors = []
    nocc = exinfo = noconf = nocompress = printonly = dontquery = notatty = 0
    queryonly = template = 0
    reportinfo = editor = None
    bts = 'debian'
    mta = '/usr/sbin/sendmail'
    use_ldap = 0 # Default to not use LDAP

    if commands.getoutput('tty') == 'not a tty':
        dontquery = 1
        notatty = printonly = 1

    args = reportbug.parse_config_files()
    for option, arg in args.items():
        if option in reportbug.CONFIG_ARGS:
            exec option + '=' +`arg`
        else:
            sys.stderr.write('Warning: untranslated token '+option+'\n')

    try:
        (opts, args) = getopt.getopt(
            sys.argv[1:],
            'habB:cC:de:f:gH:i:j:lL:mMno:pPqQs:S:t:T:uvV:xz',
            ['help', 'no-config-files', 'debug', 'editor=',
             'filename=', 'header=', 'maintonly',
             'report-quiet', 'subject=', 'no-cc',
             'mua=', 'quiet', 'class=', 'ui=',
             'include=', 'severity=', 'version', 'af',
             'mh', 'nmh', 'mutt', 'no-compress',
             'no-ldap', 'output=', 'no-query-bts',
             'bts=', 'ldap', 'gpg', 'gnupg', 'pgp',
             'email=', 'replyto=', 'reply-to=',
             'smtphost=', 'query-bts', 'query-only',
             'http_proxy=', 'proxy=', 'realname=', 'tags=',
             'list-cc=', 'type=', 'interface=', 'print',
             'justification=', 'package-version=', 'mta=', 'template'])
    except getopt.error, msg:
        if msg:
            ewrite(str(msg)+'\n')
        sys.exit(1)

    for option, arg in opts:
        if option in ('-h', '--help'):
            print USAGE
            return
        elif option in ('-v', '--version'):
            print COPYRIGHT
            return
        elif option in ('-c', '--no-config-files'):
            noconf = 1
        elif option in ('-C', '--class'):
            if arg in debianbts.CLASSLIST:
                klass = arg
            elif arg == 'help':
                ewrite('Permitted report class:\n')
                for bsys in debianbts.CLASSLIST:
                    ewrite(' %s\n', bsys)
                sys.exit(0)
            else:
                ewrite("Ignored bogus class %s\n", arg)
        elif option in ('-d', '--debug'):
            sendto = 'lawrencc@debian.org'
        elif option in ('-e', '--editor'):
            editor = arg
        elif option in ('-f', '--filename'):
            searchfor = arg
        elif option in ('-g', '--gnupg', '--gpg'):
            sign = 'gpg'
        elif option in ('-H', '--header'):
            headers.append(arg)
        elif option in ('-m', '--maintonly'):
            sendto = 'maintonly'
        elif option in ('-M', '--mutt'):
            mua = 'mutt -H'
        elif option in ('-a', '--af'):
            mua = 'af -EH <'
        elif option in ('-n', '--mh', '--nmh'):
            mua = '/usr/bin/mh/comp -use -file'
        elif option == '--mua':
            mua = arg
        elif option == '--mta':
            mta = arg
        elif option in ('-c', '--list-cc'):
            headers.append('X-Debbugs-Cc: '+arg)
        elif option in ('-p', '--print'):
            printonly = 1
        elif option in ('-P', '--pgp'):
            sign = 'pgp'
        elif option == 'report-quiet':
            sendto = 'quiet'
        elif option in ('-q', '--quiet'):
            quietly = 1
        elif option in ('-s', '--subject'):
            subject = arg
        elif option in ('-x', '--no-cc'):
            nocc = 1
        elif option in ('-z', '--no-compress'):
            nocompress = 1
        elif option in ('-o', '--output'):
            outfile = arg
            mua = ''
            printonly = 0
        elif option in ('-i', '--include'):
            include.append(arg)
        elif option in ('-b', '--no-query-bts'):
            dontquery = 1
        elif option == '--query-bts':
            dontquery = 0
        elif option == '--no-ldap':
            use_ldap = 0
        elif option in ('-l', '--ldap'):
            use_ldap = 1
        elif option in ('-T', '--tags'):
            tags = arg
        elif option in ('--proxy', '--http_proxy'):
            http_proxy = arg
        elif option == '--email':
            email = arg
        elif option == '--realname':
            realname = arg
        elif option in ('--replyto', '--reply-to'):
            replyto = arg
        elif option == '--smtphost':
            smtphost = arg
        elif option in ('-j', '--justification'):
            justification = arg
        elif option in ('-V', '--package-version'):
            pkgversion = arg
        elif option in ('-u', '--ui', '--interface'):
            if arg in reportbug.VALID_UIS:
                interface = arg
            elif arg == 'help':
                ewrite('Permitted arguments to --ui:\n'
                       ' text: line-oriented text mode\n'
                       ' newt: screen-oriented text mode\n')
                sys.exit(0)
            else:
                ewrite("Ignoring unknown user interface %s\n", arg)
        elif option in ('-Q', '--query-only'):
            queryonly = 1
        elif option in ('-t', '--type'):
            if arg in ('gnats', 'debbugs'):
                type = arg
            elif arg == 'help':
                ewrite('Permitted arguments to --type:\n'
                       ' debbugs: Debian bug tracking system\n'
                       ' gnats:   GNU Problem Report Management System\n')
                sys.exit(0)
            else:
                ewrite("Ignoring unknown reporting type %s\n", arg)
        elif option in ('-B', '--bts'):
            if arg in debianbts.SYSTEMS.keys():
                force_bts = bts = arg
            elif arg == 'help':
                ewrite('Permitted arguments to --bts:\n')
                names = debianbts.SYSTEMS.keys()
                names.sort()
                for bsys in names:
                    ewrite(' %-11.11s %s\n',bsys,
                           debianbts.SYSTEMS[bsys]['name'])
                sys.exit(0)
            else:
                ewrite("Ignoring unknown BTS %s\n", arg)
        elif option in ('-S', '--severity'):
            if arg in debianbts.SEVLIST:
                severity = arg
            elif arg == 'help':
                ewrite('Permitted severity levels:\n')
                for bsys in debianbts.SEVLIST:
                    ewrite(' %s\n', bsys)
                sys.exit(0)
            else:
                ewrite("Ignored bogus severity level %s\n", arg)
        elif option == '--template':
            template = 1

    if template:
        printonly = dontquery = quietly = notatty = 1
        tags = tags or 'none'
        severity = severity or 'wishlist'
        subject = subject or 'none'

    if not type:
        type = debianbts.SYSTEMS[bts]['type']

    # Disable signatures when in printonly or mua mode
    # (since they'll be bogus anyway)
    if (mua or printonly) and sign:
        sign = ''

    # Disable smtphost if mua is set
    if mua and smtphost:
        smtphost = ''

    sysinfo = debianbts.SYSTEMS[bts]

    uid = os.getuid()
    if uid < 250:
        if notatty and not uid: 
            ewrite("reportbug will not run as root non-interactively.\n")
            sys.exit(1)

        if not uid:
            x = ui.select_options("Running 'reportbug' as root is probably "
                                  "insecure!\nContinue", 'yN',
                                  {'y': 'Continue with reportbug.',
                                   'n': 'Exit.'})
        else:
            x = ui.select_options("Running 'reportbug' as an administrative "
                                  "user is probably not a good idea.  "
                                  "Continue", 'yN',
                                  {'y': 'Continue with reportbug.',
                                   'n': 'Exit.'})
        
        if x == 'n':
            ewrite("reportbug stopped.\n")
            sys.exit(1)

    if interface:
        iface = 'reportbug_ui_'+interface
        exec 'import '+iface
        ui = eval(iface)

    foundfile = None
    if len(args) == 0 and not searchfor and not notatty:
        package = get_package_name(bts)
    elif len(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 searchfor:
        (foundfile, package) = find_package_for(searchfor, notatty)
    elif len(args):
        package = args[0]

    if package and ('/' in package):
        (foundfile, package) = find_package_for(package, notatty)

    if not package:
        ewrite("No package specified; stopping.\n")
        sys.exit(1)

    fromaddr = reportbug.get_user_id(email, realname)
    ewrite("Using '%s' as your from address.\n", fromaddr)

    if mua and string.find(fromaddr, 'Ajay Shah'):
        ui.long_message("NOTE: MANY MUAs WILL ATTEMPT TO SEND MIME-ENCODED "
                        "MESSAGES TO THE DEBIAN BTS.  THE DEBIAN BTS "
                        "CURRENTLY HAS BUGS IN ITS HANDLING OF MIME (SEE "
                        "#36813 et al.).  YOU SHOULD NOT USE MIME ATTACHMENTS "
                        "IN YOUR REPORT.\n")

    buginfo = '/usr/share/bug/' + package
    bugexec = submitas = submitto = presubj = None
    if 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 = reportbug.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 = reportbug.parse_bug_control_file(buginfo+
                                                                  '/control')

    incfiles = ""
    if include:
        for file in include:
            if os.path.exists(file):
                fp = open(file)
                incfiles = '%s\n*** %s\n%s' % (incfiles, file, fp.read())
                fp.close()
            else:
                ewrite("Can't find %s to include!\n", file)
                sys.exit(1)

    pkgavail = depends = maintainer = ''
    conffiles = []
    isvirtual = package in sysinfo['otherpkgs'].keys()
    issource = 0
    if not pkgversion and sysinfo['query-dpkg']:
        ewrite("Getting status for %s...\n", package)
        status = reportbug.get_package_status(package)
        pkgavail, installed = status[1], status[5]

        if not pkgavail and not isvirtual:
            # Look for a matching source package
            packages = reportbug.get_source_package(package)
            if len(packages) > 0:
                src = package
                if len(packages) == 1:
                    ewrite('Source package "%s" corresponds to '
                           'binary package "%s"\n', package, packages[0][0])
                    package = packages[0][0]
                elif len(packages) and not notatty:
                    packages.append( (src, 'Source package') )
                    package = ui.menu('Which of the following packages is the '
                                      'bug in?', packages,
                                      'Select one of these packages: ')
                if not package:
                    ewrite("No package specified; stopping.\n")
                    sys.exit(1)

                if package != src:
                    ewrite("Getting status for %s...\n", package)
                    status = reportbug.get_package_status(package)
                else:
                    issource = 1
            else:
                ewrite('No matching source or binary packages.\n')
            
        if not installed and not notatty and not isvirtual and not issource:
            packages = reportbug.packages_providing(package)
            if len(packages) == 0:
                response = ui.select_options(
                    'This package does not appear to be installed; continue',
                    'yN', {'y': 'Ignore this problem and continue.',
                           'n': 'Exit without filing a report.'})
                if response == 'n':
                    ewrite("Package not installed; stopping.\n")
                    sys.exit(1)
            else:
                this_package = [(package, 'Uninstalled/non-existent package')]
                package = ui.menu('Which of the following installed packages '
                                  'is the bug in?', packages + this_package,
                                  'Select one of these packages: ')
                if not package:
                    ewrite("No package specified; stopping.\n")
                    sys.exit(1)
                else:
                    ewrite("Getting status for %s...\n", package)
                    status = reportbug.get_package_status(package)
        elif not pkgavail and not notatty and not isvirtual and not issource:
            response = ui.select_options(
                'This package does not appear to exist; continue', 'yN',
                {'y': 'Ignore this problem and continue.  This may be '
                 'appropriate if you are reporting a bug in a source package.',
                 'n': 'Exit without filing a report.' })
            if response == 'n':
                ewrite("Package does not exist; stopping.\n")
                sys.exit(1)

        (pkgversion, pkgavail, depends, conffiles, maintainer, installed,
         origin, vendor, reportinfo, priority, desc, src_name,
         fulldesc) = status

    if not pkgversion:
        pkgversion = time.strftime("N/A; reported %Y-%m-%d",
                                   time.localtime(time.time())) 

    if force_bts:
        bts = force_bts
        ewrite("Will send report to %s.\n", debianbts.SYSTEMS[bts]['name'])
    elif origin:
        if origin == bts:
            ewrite("Package originates from %s.\n", vendor)
            reportinfo = None
        elif origin in debianbts.SYSTEMS.keys():
            ewrite("Package originates from %s; overriding your system "
                   "selection.\n", vendor)
            bts = origin
            reportinfo = None
        elif reportinfo:
            ewrite("Unknown origin %s; will send to %s.\n", origin,
                   reportinfo[1])
            type, 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: 
        type, submitto = reportinfo
        ewrite("Will use %s protocol talking to %s.\n", type, submitto)
        # for now
        dontquery = 1

    if type == 'mailto':
        type = 'debbugs'
        dontquery = 1

    if not dontquery:
        exinfo = ui.handle_bts_query(package, bts, use_ldap, mirrors,
                                     http_proxy, source=issource)
        if queryonly: return
        if exinfo and exinfo < 0:
            ewrite('Nothing new to report; exiting.\n')
            return

    ccaddr = os.environ.get('MAILCC')
    # Ignore this if someone's specifying a compiler
    if ccaddr and '@' not in ccaddr: ccaddr = None
    bccaddr = os.environ.get('MAILBCC', fromaddr)

    if maintainer:
        ewrite("Maintainer for %s is '%s'.\n", package, maintainer)

    depinfo = ""
    # Grab dependency list, removing version conditions.
    if depends:
        ewrite("Getting dependency information for %s...\n", package)
        depinfo = reportbug.get_dependency_info(package, depends)

    confinfo = []
    conftext = ''
    remonly = 0
    if conffiles:
        ewrite("Getting changed configuration files...\n")
        confinfo, changed = reportbug.get_changed_config_files(conffiles,
                                                               nocompress)

    if not conffiles:
        pass
    elif noconf and changed:
        for file in changed:
            confinfo[file] = 'changed [not included]'
    elif changed and not notatty:
        while 1:
            x = ui.select_options(
                "*** WARNING: The following configuration files have been "
                "modified:\n"+ string.join(changed, '\n')+
                "\nSend modified configuration files", 'Ynd',
                {'y':'Send your modified configuration files.',
                 'n':"Don't send modified configuration files.",
                 'd':'Display modified configuration files.'})
            if x == 'n':
                for file in changed:
                    confinfo[file] = 'changed [not included]'
                break
            elif x == 'd':
                PAGER = os.environ.get('PAGER', '/usr/bin/sensible-pager')
                system(PAGER+' '+string.join(changed, ' '))
            else:
                break   

    if confinfo:
        conftext = '\n-- Configuration Files:\n'
        files = confinfo.keys()
        files.sort()
        for file in files:
            conftext = conftext + '%s %s\n' % (file, confinfo[file])

    prompted = 0
    if exinfo:
        prompted = 1
        subject = ui.get_string('Please provide a subject for your response; '
                                'no subject will stop reportbug.',
                                force_prompt=1)
    elif not subject and not notatty:
        res = special_prompts(package, bts, ui)
        if res:
            (subject, severity, h, body) = res
            headers = headers + h
        else:
            prompted = 1
            if presubj:
                ewrite(open(presubj).read()+'\n')

            subject = ui.get_string(
                'Please briefly describe your problem (you can elaborate in '
                'a moment; an empty response will stop reportbug).',
                force_prompt=1)

    if not subject:
        ewrite("No subject specified; stopping.\n")
        sys.exit(1)

    if len(subject) > 100 and prompted:
        subject = ui.get_string(
            'Your description is a bit long; please enter a shorter subject. '
            '(An empty response will retain the existing subject.)',
            force_prompt=1) or subject
    elif package != 'wnpp' and prompted:
        if (not re.search(r"^\S+:\s", subject) and
            string.find(subject, package) != 0):
            subject = package + ": " + subject
            ewrite("Rewriting subject to '%s'\n", subject)

    if severity and type:
        severity = debianbts.convert_severity(severity, type)
    
    if not notatty and not exinfo:
        if not severity:
            if type == 'gnats':
                severities = debianbts.SEVERITIES_gnats
                default = 'non-critical'
            else:
                severities = debianbts.SEVERITIES
                default = 'normal'
            
            severity = ui.menu("How would you rate the severity of this "
                               "problem or report?", severities,
                               'Please select a severity level: ',
                               default=default, order=debianbts.SEVLIST)

        if type == 'gnats':
            # Class of report
            klass = ui.menu("What sort of problem are you reporting?",
                            debianbts.CLASSES, 'Please select a class: ',
                            default='sw-bug', order=debianbts.CLASSLIST)

    severity = severity or 'normal'

    if type == 'debbugs' and package != 'wnpp':
        if severity in ('critical', 'grave'):
            justification = ui.menu(
                'You are reporting a ' +severity+' bug; which of the '
                'following criteria does it meet?',
                debianbts.JUSTIFICATIONS[severity],
                'Please select the impact of the bug: ', default='unknown')
        elif severity == 'serious':
            while 1:
                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.', force_prompt=1)
                if re.match('[0-9]+\.[0-9.]+', justification):
                    justification = 'Policy ' + justification
                    break
                elif justification == 'unknown':
                    break
                ewrite("That isn't a valid response.\n")
                
        if justification == 'unknown':
            justification = ''
            severity = 'normal'
            ewrite('Severity downgraded to "normal".\n')

    if severity in ('wishlist', 'minor') and sendto == 'submit' and \
       type == 'debbugs' and package != 'wnpp' and not notatty:
        monly = ui.select_options(
            'You are reporting a '+severity+' bug; send it to the maintainer '
            'only', 'Yn',
            {'y': 'Send report to just the maintainer.',
             'n': 'Send report to maintainer and bugs mailing list.'})
        if monly == 'y': sendto = 'maintonly'
    
    tempfile.tempdir = TMPDIR = os.environ.get('TMPDIR', '/tmp')
    tempfile.template = 'reportbug-'+`os.getpid()`+'-'

    if (type == 'debbugs' and not tags and not notatty and
        not exinfo and package != 'wnpp'):
        tag = ''
        while tag != 'done':
            tag = ui.menu('Do any of the following apply to this report?',
                          debianbts.TAGS, 'Please select a tag: ',
                          default='done', order=debianbts.TAGLIST,
                          extras=debianbts.EXTRA_TAGS)
            if tag != 'done':
                ewrite('Adding tag %s\n', tag)
                tags = tags + ' '+tag
        tags = string.strip(tags)

    if tags == 'none':
        tags = ''
    
    # Execute bug script
    if bugexec:
        if os.path.exists('handle_bugscript'):
            handler = './handle_bugscript'
        else:
            handler = '/usr/share/reportbug/handle_bugscript'

        filename = tempfile.mktemp()
        fh = open_write_safe(filename, 'w')
        system(handler+' '+bugexec+' '+filename)
        fh.close()

        addinfo = open(filename).read()
        os.unlink(filename)
        if addinfo: incfiles =  addinfo + '\n' + incfiles

    # Prepare bug report
    message = reportbug.generate_blank_report(
        submitas or package, pkgversion, severity, justification, depinfo,
        conftext, foundfile, incfiles, bts, exinfo, type, klass, subject,
        tags, body)

    # 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, sendto)
                else:
                    sendto = str(exinfo)

            sendto = sendto+'@'+submitto
    elif '@' not in sendto:
        if exinfo:
            if sendto != 'submit':
                sendto = '%d-%s' % (exinfo, sendto)
            else:
                sendto = str(exinfo)

        try:
            sendto = sysinfo['email'] % sendto
        except TypeError:
            sendto = sysinfo['email']
            
        name = reportbug.quote_if_needed(sysinfo['name']+
                                         ' Bug Tracking System')
        sendto = '%s <%s>' % (name, sendto)

    filename = tempfile.mktemp()

    pseudoheaders = []
    if printonly:
        report = sys.stdout
    elif mua:
        report = open_write_safe(filename, 'w')
    else:
        dmessage = "Subject: %s\n%s" % (subject, message)
        
        open_write_safe(filename, 'w').write(dmessage)
        message = None
        while 1:
            (message, changed) = spawn_editor(message or dmessage, filename,
                                              editor)
            if not message:
                x = ''
                while x != 'y':
                    x = ui.select_options(
                        'Done editing', 'Ynq',
                        {'y': 'Continue (editing done).',
                         'n': "Don't continue yet.",
                         'q': 'Exit without sending report.'})
                    if x == 'q':
                        stopmsg(filename)
                        sys.exit(1)
                
                message = file.open(filename).read()
                changed = 1
            
            if outfile:
                ewrite("Report will be saved as %s\n", outfile)
            else:
                ewrite("Report will be sent to %s\n", sendto)

            options = "Ynie"
            if not changed: options = "yniE"
            
            x = ui.select_options(
                'Submit this bug report (e to edit)', options,
                {'y': 'Submit the bug report via email.',
                 'n': "Don't submit the bug report; "
                 "instead, save it in a temporary file.",
                 'i': "Include a text file.",
                 'e': 'Re-edit the bug report.'})
            if x == 'i':
                file = ui.get_string('Choose a text file to include: ')
                if file:
                    if os.path.exists(file):
                        fp = open(file)
                        message = message + '\n*** %s\n%s' % (file, fp.read())
                        fp.close()
                    else:
                        ewrite("Can't find %s to include!\n", file)
            elif x == 'n':
                stopmsg(filename)
                sys.exit(1)
            elif x == 'y':
                if message == dmessage:
                    options = 'nE'
                    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':
                        stopmsg(filename)
                        sys.exit(1)
                        break
                    elif x == 's':
                        ewrite('Sending empty report anyway...\n')
                        break
                else:
                    break

        message = open(filename).read()
        
        if not sendto:
            print message,
            # Remove the temporary file
            if os.path.exists(filename): os.unlink(filename)
            return

        # Handle non-pseduo-headers
        headerre = re.compile(r'^([^:]+):\s*(.*)$', re.I)
        lines = string.split(message, '\n')
        newsubject = message = ''
        parsing = lastpseudo = 1
        for line in lines:
            if not line and parsing: parsing = 0
            elif parsing:
                mob = headerre.match(line)
                # GNATS and debbugs have different ideas of what a pseudoheader
                # is...
                if mob and ((type == 'debbugs' and
                             mob.group(1) not in reportbug.PSEUDOHEADERS) or
                            (type == 'gnats' and mob.group(1)[0] != '>')):
                    if string.lower(mob.group(1)) == 'subject':
                        # Continuation subject lines probably due to reporter
                        # being clueless
                        newsubject = mob.group(2)
                        lastpseudo = 1
                    else:
                        headers.append('%s: %s' % mob.groups())
                        lastpseudo = 0
                    continue
                elif mob:
                    # Normalize pseudo-header
                    lastpseudo = 0
                    key, value = mob.groups()
                    if key[0] != '>':
                        key = string.join(map(string.capitalize,
                                              string.split(key, '-')), '-')
                    pseudoheaders.append((key, value))
                elif not lastpseudo and len(headers):
                    # Assume we have a continuation line
                    headers[-1] = headers[-1] + '\n' + line
                    continue
                else:
                    # Permit bogus headers in the pseudoheader section
                    headers.append(line)
            else:
                message = message + line + '\n'

        ph = ''
        if type == 'gnats':
            for header, content in pseudoheaders:
                if content:
                    ph = ph + "%s: %s\n" % (header, content)
                else:
                    ph = ph + content + "\n"
        else:
            ph2 = {}
            for header, content in pseudoheaders:
                ph2[header] = content
                    
            for header in reportbug.PSEUDOHEADERS:
                if ph2.has_key(header):
                    ph = ph + '%s: %s\n' % (header, ph2[header])

        if ph:
            message = ph + '\n' + message

        if newsubject:
            subject = newsubject

    if ccaddr:
        headers.append('Cc: '+ccaddr)

    if not nocc:
        headers.append('Bcc: '+bccaddr)

    replyto = os.environ.get("REPLYTO", replyto)
    if replyto:
        headers.append('Reply-To: '+replyto)

    # Standard headers
    if not mua and not printonly:
        headers = ['X-Mailer: '+MUA,
                   'Date: '+reportbug.rfcdatestr()] + headers
    if not printonly and mua:
        headers = ['X-Reportbug-Version: ##VERSION##'] + headers

    headers = ['From: '+fromaddr,
               'To: '+sendto,
               'Subject: '+subject] + headers

    failed = 0
    usingpipe = 0
    msgname = ''
    if mua or printonly:
        pipe = report
    elif outfile or not os.path.exists(mta):
        msgname = outfile or ('/var/tmp/%s.bug' % package)
        if os.path.exists(msgname): os.rename(msgname, msgname+'~')
        try:
            pipe = open_write_safe(msgname, 'w')
        except OSError:
            for dir in (TMPDIR, '/var/tmp', '/tmp',
                        os.environ.get('HOME', '/')):
                msgname = dir+'/%s.bug' % package
                if os.path.exists(msgname): os.rename(msgname, msgname+"~")
                ewrite('Writing to file failed; '
                       'writing bug report to %s\n', msgname)
                try:
                    pipe = open_write_safe(msgname, 'w')
                    break
                except (IOError, OSError):
                    ewrite('Unable to write %s.\n', msgname)
            
    elif not smtphost:
        try:
            x = os.getcwd()
        except OSError:
            os.chdir('/')
        pipe = os.popen(mta+' -t -oi -oem', 'w')
        usingpipe = 1

    if sign:
        ewrite('Passing message to %s for signature...\n', sign)
        if pseudoheaders:
            try:
                pseudoheaders, message = string.split(message, '\n\n', 1)
            except:
                pseudoheaders = ''
        else:
            pseudoheaders = ''
        
        tmpfile2 = tempfile.mktemp()
        open_write_safe(tmpfile2, 'w').write(message)
        if sign == 'gpg':
            system("gpg --clearsign '"+tmpfile2+"'")
        elif sign == 'pgp':
            system("pgp -sat '"+tmpfile2+"'")

        ascfile = tmpfile2+'.asc'
        if not os.path.exists(ascfile):
            ewrite('gpg/pgp failed; input file in %s\n', tmpfile2)
            os.exit(1)

        if pseudoheaders:
            message = pseudoheaders + '\n\n' + open(ascfile).read()
        os.unlink(ascfile)
        os.unlink(tmpfile2)

    message = string.join(headers, '\n') + '\n\n' + message
        
    msg = rfc822.Message(cStringIO.StringIO(message))
    alist = msg.getaddrlist('To')
    alist = alist + msg.getaddrlist('Cc')
    alist = alist + msg.getaddrlist('Bcc')

    if smtphost:
        toaddrs = map(lambda x: x[1], alist)
        # print toaddrs

        ewrite("Connecting to %s...\n", smtphost)
        try:
##            raise smtplib.SMTPException
            conn = smtplib.SMTP(smtphost)
            conn.sendmail(fromaddr, toaddrs, message)
            conn.quit()
        except (socket.error, smtplib.SMTPException), x:
            failed = 1
            for dir in (TMPDIR, '/var/tmp', '/tmp',
                        os.environ.get('HOME', '/')):
                msgname = dir+'/%s.bug' % package
                ewrite('SMTP send failure: %s\n', x)
                ewrite('Writing bug report to %s\n', msgname)
                if os.path.exists(msgname): os.rename(msgname, msgname+"~")
                try:
                    open_write_safe(msgname, 'w').write(message)
                    break
                except (IOError, OSError):
                    ewrite('Unable to write %s.\n', msgname)

        del msg
    else:
        pipe.write(message)

        if msgname:
            ewrite("Bug report written as %s\n", msgname)
    
        if pipe.close() and usingpipe:
            failed = 1
            for dir in (TMPDIR, '/var/tmp', '/tmp',
                        os.environ.get('HOME', '/')):
                msgname = dir+'/%s.bug' % package
                if os.path.exists(msgname): os.rename(msgname, msgname+"~")
                ewrite(mta+' failed; writing bug report to %s\n', msgname)
                try:
                    open_write_safe(msgname, 'w').write(message)
                    break
                except (IOError, OSError):
                    ewrite('Unable to write %s.\n', msgname)

    if mua:
        ewrite("Spawning %s...\n", string.split(mua, ' ')[0])
        system("%s %s" % (mua, filename))
    elif not failed and (usingpipe or smtphost):
        ewrite("\nBug report submitted to: %s\n", sendto)

        addresses = []
        for addr in alist:
            if addr[1] != rfc822.parseaddr(sendto)[1]:
                addresses.append(addr)
            
        if len(addresses):    
            ewrite("Copies sent to:\n")
            for address in addresses:
                ewrite('  %s <%s>\n', rfc822.quote(address[0]),
                       address[1])
            
        if not exinfo and type == 'debbugs':
            ewrite(
"""\nIf you want to submit further information about this bug, please wait to
receive the bug tracking number via email.  Then send any extra information
to %s (e.g. %s), where n is the bug number.\n""",
            (sysinfo['email'] % 'n'), (sysinfo['email'] % '107621'))

    # Remove the temporary file (if something hasn't killed it already)
    if os.path.exists(filename):
        os.unlink(filename)

    return

if __name__ == '__main__':
    try:
        main()
    except KeyboardInterrupt:
        ewrite("\nreportbug: exiting due to user interrupt.\n")
    except debianbts.Error, x:
        ewrite('error accessing BTS: '+str(x)+'\n')
