Blame src/daemon/abrt-handle-upload.in

Packit 8ea169
#!/usr/bin/python3 -u
Packit 8ea169
# Called by abrtd when a new file is noticed in upload directory.
Packit 8ea169
# The task of this script is to unpack the file and move
Packit 8ea169
# problem data found in it to abrtd spool directory.
Packit 8ea169
Packit 8ea169
import sys
Packit 8ea169
import stat
Packit 8ea169
import os
Packit 8ea169
import getopt
Packit 8ea169
import tempfile
Packit 8ea169
import shutil
Packit 8ea169
import datetime
Packit 8ea169
import grp
Packit 8ea169
Packit 8ea169
from reportclient import set_verbosity, error_msg_and_die, error_msg, log_warning
Packit 8ea169
Packit 8ea169
GETTEXT_PROGNAME = "abrt"
Packit 8ea169
import locale
Packit 8ea169
import gettext
Packit 8ea169
Packit 8ea169
_ = lambda x: gettext.gettext(x)
Packit 8ea169
Packit 8ea169
def init_gettext():
Packit 8ea169
    try:
Packit 8ea169
        locale.setlocale(locale.LC_ALL, "")
Packit 8ea169
    except locale.Error:
Packit 8ea169
        os.environ['LC_ALL'] = 'C'
Packit 8ea169
        locale.setlocale(locale.LC_ALL, "")
Packit 8ea169
    # Defeat "AttributeError: 'module' object has no attribute 'nl_langinfo'"
Packit 8ea169
    try:
Packit 8ea169
        gettext.bind_textdomain_codeset(GETTEXT_PROGNAME, locale.nl_langinfo(locale.CODESET))
Packit 8ea169
    except AttributeError:
Packit 8ea169
        pass
Packit 8ea169
    gettext.bindtextdomain(GETTEXT_PROGNAME, '/usr/share/locale')
Packit 8ea169
    gettext.textdomain(GETTEXT_PROGNAME)
Packit 8ea169
Packit 8ea169
Packit 8ea169
import problem
Packit 8ea169
Packit 8ea169
def write_bytes_to(filename, b, uid, gid, mode):
Packit 8ea169
    fd = os.open(filename, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, mode)
Packit 8ea169
    if fd >= 0:
Packit 8ea169
        os.fchown(fd, uid, gid)
Packit 8ea169
        os.write(fd, b)
Packit 8ea169
        os.close(fd)
Packit 8ea169
Packit 8ea169
Packit 8ea169
def validate_transform_move_and_notify(uploaded_dir_path, problem_dir_path, dest=None):
Packit 8ea169
    fsuid = 0
Packit 8ea169
    fsgid = 0
Packit 8ea169
Packit 8ea169
    try:
Packit 8ea169
        gabrt = grp.getgrnam("abrt")
Packit 8ea169
        fsgid = gabrt.gr_gid
Packit 8ea169
    except KeyError as ex:
Packit 8ea169
        error_msg("Failed to get GID of 'abrt' (using 0 instead): {0}'".format(str(ex)))
Packit 8ea169
Packit 8ea169
    try:
Packit 8ea169
        # give the uploaded directory to 'root:abrt' or 'root:root'
Packit 8ea169
        os.chown(uploaded_dir_path, fsuid, fsgid)
Packit 8ea169
        # set the right permissions for this machine
Packit 8ea169
        # (allow the owner and the group to access problem elements,
Packit 8ea169
        #  the default dump dir mode lacks x bit for both)
Packit 8ea169
        os.chmod(uploaded_dir_path, @DEFAULT_DUMP_DIR_MODE@ | stat.S_IXUSR | stat.S_IXGRP)
Packit 8ea169
Packit 8ea169
        # sanitize problem elements
Packit 8ea169
        for item in os.listdir(uploaded_dir_path):
Packit 8ea169
            apath = os.path.join(uploaded_dir_path, item)
Packit 8ea169
            if os.path.islink(apath):
Packit 8ea169
                # remove symbolic links
Packit 8ea169
                os.remove(apath)
Packit 8ea169
            elif os.path.isdir(apath):
Packit 8ea169
                # remove directories
Packit 8ea169
                shutil.rmtree(apath)
Packit 8ea169
            elif os.path.isfile(apath):
Packit 8ea169
                # set file ownership to 'root:abrt' or 'root:root'
Packit 8ea169
                os.chown(apath, fsuid, fsgid)
Packit 8ea169
                # set the right file permissions for this machine
Packit 8ea169
                os.chmod(apath, @DEFAULT_DUMP_DIR_MODE@)
Packit 8ea169
            else:
Packit 8ea169
                # remove things that are neither files, symlinks nor directories
Packit 8ea169
                os.remove(apath)
Packit 8ea169
    except OSError as ex:
Packit 8ea169
        error_msg("Removing uploaded dir '{0}': '{1}'".format(uploaded_dir_path, str(ex)))
Packit 8ea169
        try:
Packit 8ea169
            shutil.rmtree(uploaded_dir_path)
Packit 8ea169
        except OSError as ex2:
Packit 8ea169
            error_msg_and_die("Failed to clean up dir '{0}': '{1}'".format(uploaded_dir_path, str(ex2)))
Packit 8ea169
        return
Packit 8ea169
Packit 8ea169
    # overwrite remote if it exists
Packit 8ea169
    remote_path = os.path.join(uploaded_dir_path, "remote")
Packit 8ea169
    write_bytes_to(remote_path, b"1", fsuid, fsgid, @DEFAULT_DUMP_DIR_MODE@)
Packit 8ea169
Packit 8ea169
    # abrtd would increment count value and abrt-server refuses to process
Packit 8ea169
    # problem directories containing 'count' element when PrivateReports is on.
Packit 8ea169
    count_path = os.path.join(uploaded_dir_path, "count")
Packit 8ea169
    if os.path.exists(count_path):
Packit 8ea169
        # overwrite remote_count if it exists
Packit 8ea169
        remote_count_path = os.path.join(uploaded_dir_path, "remote_count")
Packit 8ea169
        os.rename(count_path, remote_count_path)
Packit 8ea169
Packit 8ea169
    if not dest:
Packit 8ea169
        dest = problem_dir_path
Packit 8ea169
Packit 8ea169
    shutil.move(uploaded_dir_path, dest)
Packit 8ea169
Packit 8ea169
    problem.notify_new_path(problem_dir_path)
Packit 8ea169
Packit 8ea169
Packit 8ea169
if __name__ == "__main__":
Packit 8ea169
Packit 8ea169
    # Helper: exit with cleanup
Packit 8ea169
    die_exitcode = 1
Packit 8ea169
    delete_on_exit = None
Packit 8ea169
    def print_clean_and_die(fmt, *args):
Packit 8ea169
        sys.stderr.write("%s\n" % (fmt % args))
Packit 8ea169
        if delete_on_exit:
Packit 8ea169
            shutil.rmtree(delete_on_exit, True) # True: ignore_errors
Packit 8ea169
        sys.exit(die_exitcode)
Packit 8ea169
Packit 8ea169
    # localization
Packit 8ea169
    init_gettext()
Packit 8ea169
Packit 8ea169
    verbose = 0
Packit 8ea169
    ABRT_VERBOSE = os.getenv("ABRT_VERBOSE")
Packit 8ea169
    if ABRT_VERBOSE:
Packit 8ea169
        try:
Packit 8ea169
            verbose = int(ABRT_VERBOSE)
Packit 8ea169
        except:
Packit 8ea169
            pass
Packit 8ea169
Packit 8ea169
    progname = os.path.basename(sys.argv[0])
Packit 8ea169
    help_text = _(
Packit 8ea169
        "Usage: %s [-vd] ABRT_SPOOL_DIR UPLOAD_DIR FILENAME"
Packit 8ea169
      "\n"
Packit 8ea169
      "\n   -v             - Verbose"
Packit 8ea169
      "\n   -d             - Delete uploaded archive"
Packit 8ea169
      "\n   ABRT_SPOOL_DIR - Directory where valid uploaded archives are unpacked to"
Packit 8ea169
      "\n   UPLOAD_DIR     - Directory where uploaded archives are stored"
Packit 8ea169
      "\n   FILENAME       - Uploaded archive file name"
Packit 8ea169
      "\n"
Packit 8ea169
    ) % progname
Packit 8ea169
Packit 8ea169
    try:
Packit 8ea169
        opts, args = getopt.getopt(sys.argv[1:], "vdh", ["help"])
Packit 8ea169
    except getopt.GetoptError as err:
Packit 8ea169
        error_msg(err)  # prints something like "option -a not recognized"
Packit 8ea169
        error_msg_and_die(help_text)
Packit 8ea169
Packit 8ea169
    delete_archive = False
Packit 8ea169
    for opt, arg in opts:
Packit 8ea169
        if opt in ("-h", "--help"):
Packit 8ea169
            print(help_text)
Packit 8ea169
            sys.exit(0)
Packit 8ea169
        if opt == "-v":
Packit 8ea169
            verbose += 1
Packit 8ea169
        if opt == "-d":
Packit 8ea169
            delete_archive = True
Packit 8ea169
Packit 8ea169
    set_verbosity(verbose)
Packit 8ea169
Packit 8ea169
    if len(args) < 3:
Packit 8ea169
        error_msg_and_die(help_text)
Packit 8ea169
Packit 8ea169
    abrt_dir = args[0]
Packit 8ea169
    upload_dir = args[1]
Packit 8ea169
    archive = args[2]
Packit 8ea169
Packit 8ea169
    if not os.path.isdir(abrt_dir):
Packit 8ea169
        error_msg_and_die(_("Not a directory: '{0}'").format(abrt_dir))
Packit 8ea169
Packit 8ea169
    if not os.path.isdir(upload_dir):
Packit 8ea169
        error_msg_and_die(_("Not a directory: '{0}'").format(upload_dir))
Packit 8ea169
Packit 8ea169
    if archive[0] == "/":
Packit 8ea169
        error_msg_and_die(_("Skipping: '{0}' (starts with slash)").format(archive))
Packit 8ea169
Packit 8ea169
    if archive[0] == ".":
Packit 8ea169
        error_msg_and_die(_("Skipping: '{0}' (starts with dot)").format(archive))
Packit 8ea169
Packit 8ea169
    if ".." in archive:
Packit 8ea169
        error_msg_and_die(_("Skipping: '{0}' (contains ..)").format(archive))
Packit 8ea169
Packit 8ea169
    if " " in archive:
Packit 8ea169
        error_msg_and_die(_("Skipping: '{0}' (contains space)").format(archive))
Packit 8ea169
Packit 8ea169
    if "\t" in archive:
Packit 8ea169
        error_msg_and_die(_("Skipping: '{0}' (contains tab)").format(archive))
Packit 8ea169
Packit 8ea169
    try:
Packit 8ea169
        os.chdir(upload_dir)
Packit 8ea169
    except OSError:
Packit 8ea169
        error_msg_and_die(_("Can't change directory to '{0}'").format(upload_dir))
Packit 8ea169
Packit 8ea169
    if archive.endswith(".tar.gz"):
Packit 8ea169
        unpacker = "gunzip"
Packit 8ea169
    elif archive.endswith(".tgz"):
Packit 8ea169
        unpacker = "gunzip"
Packit 8ea169
    elif archive.endswith(".tar.bz2"):
Packit 8ea169
        unpacker = "bunzip2"
Packit 8ea169
    elif archive.endswith(".tar.xz"):
Packit 8ea169
        unpacker = "unxz"
Packit 8ea169
    else:
Packit 8ea169
        error_msg_and_die(_("Unknown file type: '{0}'").format(archive))
Packit 8ea169
Packit 8ea169
    try:
Packit 8ea169
        working_dir = tempfile.mkdtemp(prefix="abrt_handle_upload.", dir="@LARGE_DATA_TMP_DIR@")
Packit 8ea169
    except OSError:
Packit 8ea169
        error_msg_and_die(_("Can't create working directory in '{0}'").format("@LARGE_DATA_TMP_DIR@"))
Packit 8ea169
    delete_on_exit = working_dir
Packit 8ea169
Packit 8ea169
    try:
Packit 8ea169
        tempdir = working_dir + "/remote." + datetime.datetime.now().strftime("%Y-%m-%d-%H:%M:%S.%f.") + str(os.getpid())
Packit 8ea169
        working_archive = working_dir + "/" + archive
Packit 8ea169
Packit 8ea169
        if delete_archive:
Packit 8ea169
            try:
Packit 8ea169
                shutil.move(archive, working_archive)
Packit 8ea169
            except IOError:
Packit 8ea169
                print_clean_and_die(_("Can't move '{0}' to '{1}'").format(archive, working_archive))
Packit 8ea169
        else:
Packit 8ea169
            try:
Packit 8ea169
                shutil.copy(archive, working_archive)
Packit 8ea169
            except IOError:
Packit 8ea169
                print_clean_and_die(_("Can't copy '{0}' to '{1}'").format(archive, working_archive))
Packit 8ea169
Packit 8ea169
        ex = os.spawnlp(os.P_WAIT, unpacker, unpacker, "-t", "--", working_archive)
Packit 8ea169
        if ex != 0:
Packit 8ea169
            print_clean_and_die(_("Verification error on '{0}'").format(archive))
Packit 8ea169
Packit 8ea169
        log_warning(_("Unpacking '{0}'").format(archive))
Packit 8ea169
        try:
Packit 8ea169
            os.mkdir(tempdir)
Packit 8ea169
        except OSError:
Packit 8ea169
            print_clean_and_die(_("Can't create '{0}' directory").format(tempdir))
Packit 8ea169
Packit 8ea169
        ex = os.system(unpacker+" <"+working_archive+" | tar xf - -C "+tempdir)
Packit 8ea169
        if ex != 0:
Packit 8ea169
            print_clean_and_die(_("Can't unpack '{0}'").format(archive))
Packit 8ea169
Packit 8ea169
        # The archive can contain either plain dump files
Packit 8ea169
        # or one or more complete problem data directories.
Packit 8ea169
        # Checking second possibility first.
Packit 8ea169
        if (os.path.exists(tempdir+"/analyzer") or os.path.exists(tempdir+"/type")) and os.path.exists(tempdir+"/time"):
Packit 8ea169
            validate_transform_move_and_notify(tempdir, abrt_dir+"/"+os.path.basename(tempdir), dest=abrt_dir)
Packit 8ea169
        else:
Packit 8ea169
            for d in os.listdir(tempdir):
Packit 8ea169
                if not os.path.isdir(tempdir+"/"+d):
Packit 8ea169
                    continue
Packit 8ea169
                dst = abrt_dir+"/"+d
Packit 8ea169
                if os.path.exists(dst):
Packit 8ea169
                    dst += "."+str(os.getpid())
Packit 8ea169
                if os.path.exists(dst):
Packit 8ea169
                    continue
Packit 8ea169
                validate_transform_move_and_notify(tempdir+"/"+d, dst)
Packit 8ea169
Packit 8ea169
        die_exitcode = 0
Packit 8ea169
        # This deletes working_dir (== delete_on_exit)
Packit 8ea169
        print_clean_and_die(_("'{0}' processed successfully").format(archive))
Packit 8ea169
Packit 8ea169
    except:
Packit 8ea169
        if delete_on_exit:
Packit 8ea169
            shutil.rmtree(delete_on_exit, True) # True: ignore_errors
Packit 8ea169
        raise