Example #1
0
 dest_dir = os.path.abspath(os.path.join(base_dir, current_dir))
 dest_path = os.path.join(dest_dir, os.path.basename(rel_path))
 rel_dst = dest_path.replace(base_dir, '')
 if not valid_user_path(configuration, dest_path, base_dir, True):
     logger.error(
         '%s tried to %s move to restricted path %s ! (%s)' %
         (user_id, op_name, dest_path, current_dir))
     output_objects.append({
         'object_type':
         'error_text',
         'text':
         "Invalid destination "
         "(%s expands to an illegal path)" % current_dir
     })
     moved = False
 elif not check_write_access(dest_path, parent_dir=True):
     logger.warning('%s called without write access: %s' %
                    (op_name, dest_path))
     output_objects.append({
         'object_type':
         'error_text',
         'text':
         'cannot move "%s": inside a read-only location!' % rel_dst
     })
     moved = False
 else:
     try:
         gdp_iolog(configuration, client_id, environ['REMOTE_ADDR'],
                   'modified', [rel_dst])
         if not makedirs_rec(
                 dest_dir, configuration, accept_existing=True):
Example #2
0
            output_objects.append(
                {'object_type': 'error_text', 'text'
                 : "%s: cannot remove directory '%s': Permission denied"
                 % (op_name, pattern)})
            status = returnvalues.CLIENT_ERROR

        for abs_path in match:
            relative_path = abs_path.replace(base_dir, '')
            if verbose(flags):
                output_objects.append({'object_type': 'file', 'name'
                        : relative_path})
            if not os.path.exists(abs_path):
                output_objects.append({'object_type': 'file_not_found',
                        'name': relative_path})
                continue
            if not check_write_access(abs_path):
                logger.warning('%s called without write access: %s' % \
                               (op_name, abs_path))
                output_objects.append(
                    {'object_type': 'error_text', 'text':
                     'cannot remove "%s": inside a read-only location!' % \
                     pattern})
                status = returnvalues.CLIENT_ERROR
                continue
            try:
                if parents(flags):

                    # Please note that 'rmdir -p X' should give error if X
                    #  doesn't exist contrary to 'mkdir -p X' not giving error
                    # if X exists.
Example #3
0
def main(client_id, user_arguments_dict):
    """Main function used by front end"""

    (configuration, logger, output_objects, op_name) = \
        initialize_main_variables(client_id)
    client_dir = client_id_dir(client_id)
    status = returnvalues.OK
    defaults = signature()[1]
    (validate_status, accepted) = validate_input_and_cert(
        user_arguments_dict,
        defaults,
        output_objects,
        client_id,
        configuration,
        allow_rejects=False,
    )
    if not validate_status:
        return (accepted, returnvalues.CLIENT_ERROR)

    flags = ''.join(accepted['flags'])
    size = int(accepted['size'][-1])
    pattern_list = accepted['path']

    if not safe_handler(configuration, 'post', op_name, client_id,
                        get_csrf_limit(configuration), accepted):
        output_objects.append({
            'object_type':
            'error_text',
            'text':
            '''Only accepting
CSRF-filtered POST requests to prevent unintended updates'''
        })
        return (output_objects, returnvalues.CLIENT_ERROR)

    # Please note that base_dir must end in slash to avoid access to other
    # user dirs when own name is a prefix of another user name

    base_dir = os.path.abspath(
        os.path.join(configuration.user_home, client_dir)) + os.sep

    if verbose(flags):
        for flag in flags:
            output_objects.append({
                'object_type': 'text',
                'text': '%s using flag: %s' % (op_name, flag)
            })

    if size < 0:
        output_objects.append({
            'object_type': 'error_text',
            'text': 'size must be non-negative'
        })
        return (output_objects, returnvalues.CLIENT_ERROR)

    for pattern in pattern_list:

        # Check directory traversal attempts before actual handling to avoid
        # leaking information about file system layout while allowing
        # consistent error messages

        unfiltered_match = glob.glob(base_dir + pattern)
        match = []
        for server_path in unfiltered_match:
            # IMPORTANT: path must be expanded to abs for proper chrooting
            abs_path = os.path.abspath(server_path)
            if not valid_user_path(configuration, abs_path, base_dir, True):

                # out of bounds - save user warning for later to allow
                # partial match:
                # ../*/* is technically allowed to match own files.

                logger.warning('%s tried to %s restricted path %s ! (%s)' %
                               (client_id, op_name, abs_path, pattern))
                continue
            match.append(abs_path)

        # Now actually treat list of allowed matchings and notify if no
        # (allowed) match

        if not match:
            output_objects.append({
                'object_type': 'file_not_found',
                'name': pattern
            })
            status = returnvalues.FILE_NOT_FOUND

        for abs_path in match:
            relative_path = abs_path.replace(base_dir, '')
            if verbose(flags):
                output_objects.append({
                    'object_type': 'file',
                    'name': relative_path
                })
            if not check_write_access(abs_path):
                logger.warning('%s called without write access: %s' % \
                               (op_name, abs_path))
                output_objects.append(
                    {'object_type': 'error_text', 'text':
                     'cannot truncate "%s": inside a read-only location!' % \
                     pattern})
                status = returnvalues.CLIENT_ERROR
                continue

            try:
                fd = open(abs_path, 'r+')
                fd.truncate(size)
                fd.close()
                logger.info('%s %s %s done' % (op_name, abs_path, size))
            except Exception, exc:
                output_objects.append({
                    'object_type':
                    'error_text',
                    'text':
                    "%s: '%s': %s" % (op_name, relative_path, exc)
                })
                logger.error("%s: failed on '%s': %s" %
                             (op_name, relative_path, exc))
                status = returnvalues.SYSTEM_ERROR
                continue
Example #4
0
def main(client_id, user_arguments_dict):
    """Main function used by front end"""

    (configuration, logger, output_objects, op_name) = \
        initialize_main_variables(client_id, op_header=False)
    client_dir = client_id_dir(client_id)
    defaults = signature()[1]
    (validate_status, accepted) = validate_input_and_cert(
        user_arguments_dict,
        defaults,
        output_objects,
        client_id,
        configuration,
        allow_rejects=False,
    )
    if not validate_status:
        return (accepted, returnvalues.CLIENT_ERROR)

    flags = ''.join(accepted['flags'])
    dst = accepted['dst'][-1].lstrip(os.sep)
    pattern_list = accepted['src']
    current_dir = accepted['current_dir'][-1].lstrip(os.sep)

    # All paths are relative to current_dir

    pattern_list = [os.path.join(current_dir, i) for i in pattern_list]
    dst = os.path.join(current_dir, dst)

    if not safe_handler(configuration, 'post', op_name, client_id,
                        get_csrf_limit(configuration), accepted):
        output_objects.append(
            {'object_type': 'error_text', 'text': '''Only accepting
CSRF-filtered POST requests to prevent unintended updates'''
             })
        return (output_objects, returnvalues.CLIENT_ERROR)

    # Please note that base_dir must end in slash to avoid access to other
    # user dirs when own name is a prefix of another user name

    base_dir = os.path.abspath(os.path.join(configuration.user_home,
                                            client_dir)) + os.sep

    title_entry = find_entry(output_objects, 'title')
    title_entry['text'] = 'ZIP/TAR archiver'
    output_objects.append(
        {'object_type': 'header', 'text': 'ZIP/TAR archiver'})

    if verbose(flags):
        for flag in flags:
            output_objects.append({'object_type': 'text', 'text':
                                   '%s using flag: %s' % (op_name, flag)})

    if 'h' in flags:
        usage(output_objects)

    # IMPORTANT: path must be expanded to abs for proper chrooting
    abs_dir = os.path.abspath(os.path.join(base_dir,
                                           current_dir.lstrip(os.sep)))
    if not valid_user_path(configuration, abs_dir, base_dir, True):

        # out of bounds

        output_objects.append({'object_type': 'error_text', 'text':
                               "You're not allowed to work in %s!"
                               % current_dir})
        logger.warning('%s tried to %s restricted path %s ! (%s)'
                       % (client_id, op_name, abs_dir, current_dir))
        return (output_objects, returnvalues.CLIENT_ERROR)

    output_objects.append(
        {'object_type': 'text', 'text': "working in %s" % current_dir})

    # NOTE: dst already incorporates current_dir prefix here
    # IMPORTANT: path must be expanded to abs for proper chrooting
    abs_dest = os.path.abspath(os.path.join(base_dir, dst))
    logger.info('pack in %s' % abs_dest)

    # Don't use abs_path in output as it may expose underlying
    # fs layout.

    relative_dest = abs_dest.replace(base_dir, '')
    if not valid_user_path(configuration, abs_dest, base_dir, True):

        # out of bounds

        output_objects.append(
            {'object_type': 'error_text', 'text':
             "Invalid path! (%s expands to an illegal path)" % dst})
        logger.warning('%s tried to %s restricted path %s !(%s)'
                       % (client_id, op_name, abs_dest, dst))
        return (output_objects, returnvalues.CLIENT_ERROR)

    if not os.path.isdir(os.path.dirname(abs_dest)):
        output_objects.append({'object_type': 'error_text', 'text':
                               "No such destination directory: %s"
                               % os.path.dirname(relative_dest)})
        return (output_objects, returnvalues.CLIENT_ERROR)

    if not check_write_access(abs_dest, parent_dir=True):
        logger.warning('%s called without write access: %s' %
                       (op_name, abs_dest))
        output_objects.append(
            {'object_type': 'error_text', 'text':
             'cannot pack to "%s": inside a read-only location!' %
             relative_dest})
        return (output_objects, returnvalues.CLIENT_ERROR)

    status = returnvalues.OK

    for pattern in pattern_list:

        # Check directory traversal attempts before actual handling to avoid
        # leaking information about file system layout while allowing
        # consistent error messages

        unfiltered_match = glob.glob(base_dir + pattern)
        match = []
        for server_path in unfiltered_match:
            logger.debug("%s: inspecting: %s" % (op_name, server_path))
            # IMPORTANT: path must be expanded to abs for proper chrooting
            abs_path = os.path.abspath(server_path)
            if not valid_user_path(configuration, abs_path, base_dir, True):

                # out of bounds - save user warning for later to allow
                # partial match:
                # ../*/* is technically allowed to match own files.

                logger.warning('%s tried to %s restricted path %s ! (%s)'
                               % (client_id, op_name, abs_path, pattern))
                continue
            else:
                match.append(abs_path)

        # Now actually treat list of allowed matchings and notify if no
        # (allowed) match

        if not match:
            output_objects.append({'object_type': 'error_text', 'text':
                                   "%s: cannot pack '%s': no valid src paths"
                                   % (op_name, pattern)})
            status = returnvalues.CLIENT_ERROR
            continue

        for abs_path in match:
            relative_path = abs_path.replace(base_dir, '')
            # Generally refuse handling symlinks including root vgrid shares
            if os.path.islink(abs_path):
                output_objects.append(
                    {'object_type': 'warning', 'text': """You're not allowed to
pack entire special folders like %s shared folders!""" %
                     configuration.site_vgrid_label})
                status = returnvalues.CLIENT_ERROR
                continue
            # Additionally refuse operations on inherited subvgrid share roots
            elif in_vgrid_share(configuration, abs_path) == relative_path:
                output_objects.append(
                    {'object_type': 'warning', 'text': """You're not allowed to
pack entire %s shared folders!""" % configuration.site_vgrid_label})
                status = returnvalues.CLIENT_ERROR
                continue
            elif os.path.realpath(abs_path) == os.path.realpath(base_dir):
                logger.error("%s: refusing pack home dir: %s" % (op_name,
                                                                 abs_path))
                output_objects.append(
                    {'object_type': 'warning', 'text':
                     "You're not allowed to pack your entire home directory!"
                     })
                status = returnvalues.CLIENT_ERROR
                continue
            elif os.path.realpath(abs_dest) == os.path.realpath(abs_path):
                output_objects.append({'object_type': 'warning', 'text':
                                       'overlap in source and destination %s'
                                       % relative_dest})
                status = returnvalues.CLIENT_ERROR
                continue
            if verbose(flags):
                output_objects.append(
                    {'object_type': 'file', 'name': relative_path})

            (pack_status, msg) = pack_archive(configuration, client_id,
                                              relative_path, relative_dest)
            if not pack_status:
                output_objects.append({'object_type': 'error_text',
                                       'text': 'Error: %s' % msg})
                status = returnvalues.CLIENT_ERROR
                continue
            output_objects.append({'object_type': 'text', 'text':
                                   'Added %s to %s' % (relative_path,
                                                       relative_dest)})

    output_objects.append({'object_type': 'text', 'text':
                           'Packed archive of %s is now available in %s'
                           % (', '.join(pattern_list), relative_dest)})
    output_objects.append({'object_type': 'link', 'text': 'Download archive',
                           'destination': os.path.join('..', client_dir,
                                                       relative_dest)})

    return (output_objects, status)
Example #5
0
    relative_dest = abs_dest.replace(dst_base, '')
    if not valid_user_path(configuration, abs_dest, dst_base, True):
        logger.warning('%s tried to %s restricted path %s ! (%s)'
                       % (client_id, op_name, abs_dest, dst))
        output_objects.append(
            {'object_type': 'error_text', 'text':
             "Invalid destination (%s expands to an illegal path)" % dst})
        return (output_objects, returnvalues.CLIENT_ERROR)
    # We must make sure target dir exists if called in import X mode
    if (share_id or freeze_id) and not makedirs_rec(abs_dest, configuration):
        logger.error('could not create import destination dir: %s' % abs_dest)
        output_objects.append(
            {'object_type': 'error_text', 'text':
             'cannot import to "%s" : file in the way?' % relative_dest})
        return (output_objects, returnvalues.SYSTEM_ERROR)
    if not check_write_access(abs_dest, parent_dir=True):
        logger.warning('%s called without write access: %s'
                       % (op_name, abs_dest))
        output_objects.append(
            {'object_type': 'error_text', 'text':
             'cannot copy to "%s": inside a read-only location!'
             % relative_dest})
        return (output_objects, returnvalues.CLIENT_ERROR)
    if share_id and not force(flags) and not check_empty_dir(abs_dest):
        logger.warning('%s called %s sharelink import with non-empty dst: %s'
                       % (op_name, share_id, abs_dest))
        output_objects.append(
            {'object_type': 'error_text', 'text':
             """Importing a sharelink like '%s' into the non-empty '%s' folder
will potentially overwrite existing files with the sharelink version. If you
really want that, please try import again and select the overwrite box to
Example #6
0
        for abs_path in match:
            relative_path = abs_path.replace(base_dir, '')
            if verbose(flags):
                output_objects.append({
                    'object_type': 'file',
                    'name': relative_path
                })
            if not parents(flags) and os.path.exists(abs_path):
                output_objects.append({
                    'object_type': 'error_text',
                    'text': '%s: path exist!' % pattern
                })
                status = returnvalues.CLIENT_ERROR
                continue
            if not check_write_access(abs_path, parent_dir=True):
                logger.warning('%s called without write access: %s' %
                               (op_name, abs_path))
                output_objects.append({
                    'object_type':
                    'error_text',
                    'text':
                    'cannot create "%s": inside a read-only location!' %
                    pattern
                })
                status = returnvalues.CLIENT_ERROR
                continue
            try:
                gdp_iolog(configuration, client_id, environ['REMOTE_ADDR'],
                          'created', [relative_path])
                if parents(flags):
Example #7
0
def main(client_id, user_arguments_dict):
    """Main function used by front end"""

    (configuration, logger, output_objects, op_name) = \
        initialize_main_variables(client_id, op_header=False)
    client_dir = client_id_dir(client_id)
    defaults = signature()[1]
    (validate_status, accepted) = validate_input_and_cert(
        user_arguments_dict,
        defaults,
        output_objects,
        client_id,
        configuration,
        allow_rejects=False,
    )
    if not validate_status:
        return (accepted, returnvalues.CLIENT_ERROR)

    path = accepted['path'][-1]
    chosen_newline = accepted['newline'][-1]
    submitjob = accepted['submitjob'][-1]

    if not safe_handler(configuration, 'post', op_name, client_id,
                        get_csrf_limit(configuration), accepted):
        output_objects.append({
            'object_type':
            'error_text',
            'text':
            '''Only accepting
CSRF-filtered POST requests to prevent unintended updates'''
        })
        return (output_objects, returnvalues.CLIENT_ERROR)

    if not configuration.site_enable_jobs and submitjob:
        output_objects.append({
            'object_type':
            'error_text',
            'text':
            '''Job execution is not enabled on this system'''
        })
        return (output_objects, returnvalues.SYSTEM_ERROR)

    # Please note that base_dir must end in slash to avoid access to other
    # user dirs when own name is a prefix of another user name

    base_dir = os.path.abspath(
        os.path.join(configuration.user_home, client_dir)) + os.sep

    # HTML spec dictates newlines in forms to be MS style (\r\n)
    # rather than un*x style (\n): change if requested.

    form_newline = '\r\n'
    allowed_newline = {'unix': '\n', 'mac': '\r', 'windows': '\r\n'}
    output_objects.append({
        'object_type': 'header',
        'text': 'Saving changes to edited file'
    })

    if not chosen_newline in allowed_newline.keys():
        output_objects.append({
            'object_type':
            'error_text',
            'text':
            'Unsupported newline style supplied: %s (must be one of %s)' %
            (chosen_newline, ', '.join(allowed_newline.keys()))
        })
        return (output_objects, returnvalues.CLIENT_ERROR)

    saved_newline = allowed_newline[chosen_newline]

    # Check directory traversal attempts before actual handling to avoid
    # leaking information about file system layout while allowing consistent
    # error messages

    abs_path = ''
    unfiltered_match = glob.glob(base_dir + path)
    for server_path in unfiltered_match:
        # IMPORTANT: path must be expanded to abs for proper chrooting
        abs_path = os.path.abspath(server_path)
        if not valid_user_path(configuration, abs_path, base_dir, True):
            logger.warning('%s tried to %s restricted path %s ! (%s)' %
                           (client_id, op_name, abs_path, path))
            output_objects.append({
                'object_type':
                'error_text',
                'text':
                "Invalid path! (%s expands to an illegal path)" % path
            })
            return (output_objects, returnvalues.CLIENT_ERROR)

    if abs_path == '':
        # IMPORTANT: path must be expanded to abs for proper chrooting
        abs_path = os.path.abspath(os.path.join(base_dir, path.lstrip(os.sep)))
        if not valid_user_path(configuration, abs_path, base_dir, True):
            logger.warning('%s tried to %s restricted path %s ! (%s)' %
                           (client_id, op_name, abs_path, path))
            output_objects.append({
                'object_type':
                'error_text',
                'text':
                "Invalid path! (%s expands to an illegal path)" % path
            })
            return (output_objects, returnvalues.CLIENT_ERROR)

    if not check_write_access(abs_path, parent_dir=True):
        logger.warning('%s called without write access: %s' % \
                       (op_name, abs_path))
        output_objects.append(
            {'object_type': 'error_text', 'text':
             'cannot edit "%s": inside a read-only location!' % \
             path})
        status = returnvalues.CLIENT_ERROR
        return (output_objects, returnvalues.CLIENT_ERROR)

    (owner, time_left) = acquire_edit_lock(abs_path, client_id)
    if owner != client_id:
        output_objects.append({
            'object_type': 'error_text',
            'text': "You don't have the lock for %s!" % path
        })
        return (output_objects, returnvalues.CLIENT_ERROR)

    try:
        fh = open(abs_path, 'w+')
        fh.write(user_arguments_dict['editarea'][0].replace(
            form_newline, saved_newline))
        fh.close()

        # everything ok

        output_objects.append({
            'object_type': 'text',
            'text': 'Saved changes to %s.' % path
        })
        logger.info('saved changes to %s' % path)
        release_edit_lock(abs_path, client_id)
    except Exception, exc:

        # Don't give away information about actual fs layout

        output_objects.append({
            'object_type':
            'error_text',
            'text':
            '%s could not be written! (%s)' %
            (path, str(exc).replace(base_dir, ''))
        })
        return (output_objects, returnvalues.SYSTEM_ERROR)
Example #8
0
def main(client_id, user_arguments_dict):
    """Main function used by front end"""

    (configuration, logger, output_objects, op_name) = \
        initialize_main_variables(client_id)
    client_dir = client_id_dir(client_id)
    defaults = signature()[1]
    (validate_status, accepted) = validate_input_and_cert(
        user_arguments_dict,
        defaults,
        output_objects,
        client_id,
        configuration,
        allow_rejects=False,
    )
    if not validate_status:
        return (accepted, returnvalues.CLIENT_ERROR)

    flags = ''.join(accepted['flags'])
    algo_list = accepted['hash_algo']
    max_chunks = int(accepted['max_chunks'][-1])
    pattern_list = accepted['path']
    dst = accepted['dst'][-1]
    current_dir = accepted['current_dir'][-1].lstrip(os.sep)

    # All paths are relative to current_dir

    pattern_list = [os.path.join(current_dir, i) for i in pattern_list]
    if dst:
        dst = os.path.join(current_dir, dst)

    # Please note that base_dir must end in slash to avoid access to other
    # user dirs when own name is a prefix of another user name

    base_dir = os.path.abspath(
        os.path.join(configuration.user_home, client_dir)) + os.sep

    status = returnvalues.OK

    if verbose(flags):
        for flag in flags:
            output_objects.append({
                'object_type': 'text',
                'text': '%s using flag: %s' % (op_name, flag)
            })

    # IMPORTANT: path must be expanded to abs for proper chrooting
    abs_dir = os.path.abspath(
        os.path.join(base_dir, current_dir.lstrip(os.sep)))
    if not valid_user_path(configuration, abs_dir, base_dir, True):
        output_objects.append({
            'object_type':
            'error_text',
            'text':
            "You're not allowed to work in %s!" % current_dir
        })
        logger.warning('%s tried to %s restricted path %s ! (%s)' %
                       (client_id, op_name, abs_dir, current_dir))
        return (output_objects, returnvalues.CLIENT_ERROR)

    if verbose(flags):
        output_objects.append({
            'object_type': 'text',
            'text': "working in %s" % current_dir
        })

    if dst:
        if not safe_handler(configuration, 'post', op_name, client_id,
                            get_csrf_limit(configuration), accepted):
            output_objects.append({
                'object_type':
                'error_text',
                'text':
                '''Only accepting
                CSRF-filtered POST requests to prevent unintended updates'''
            })
            return (output_objects, returnvalues.CLIENT_ERROR)

        # NOTE: dst already incorporates current_dir prefix here
        # IMPORTANT: path must be expanded to abs for proper chrooting
        abs_dest = os.path.abspath(os.path.join(base_dir, dst))
        logger.info('chksum in %s' % abs_dest)

        # Don't use abs_path in output as it may expose underlying
        # fs layout.

        relative_dest = abs_dest.replace(base_dir, '')
        if not valid_user_path(configuration, abs_dest, base_dir, True):
            output_objects.append({
                'object_type':
                'error_text',
                'text':
                "Invalid path! (%s expands to an illegal path)" % dst
            })
            logger.warning('%s tried to %s restricted path %s !(%s)' %
                           (client_id, op_name, abs_dest, dst))
            return (output_objects, returnvalues.CLIENT_ERROR)
        if not check_write_access(abs_dest, parent_dir=True):
            logger.warning('%s called without write access: %s' %
                           (op_name, abs_dest))
            output_objects.append({
                'object_type':
                'error_text',
                'text':
                'cannot checksum to "%s": inside a read-only location!' %
                relative_dest
            })
            return (output_objects, returnvalues.CLIENT_ERROR)

    all_lines = []
    for pattern in pattern_list:

        # Check directory traversal attempts before actual handling to avoid
        # leaking information about file system layout while allowing
        # consistent error messages

        unfiltered_match = glob.glob(base_dir + pattern)
        match = []
        for server_path in unfiltered_match:
            # IMPORTANT: path must be expanded to abs for proper chrooting
            abs_path = os.path.abspath(server_path)
            if not valid_user_path(configuration, abs_path, base_dir, True):

                # out of bounds - save user warning for later to allow
                # partial match:
                # ../*/* is technically allowed to match own files.

                logger.warning('%s tried to %s restricted path %s ! (%s)' %
                               (client_id, op_name, abs_path, pattern))
                continue
            match.append(abs_path)

        # Now actually treat list of allowed matchings and notify if no
        # (allowed) match

        if not match:
            output_objects.append({
                'object_type': 'file_not_found',
                'name': pattern
            })
            status = returnvalues.FILE_NOT_FOUND

        for abs_path in match:
            relative_path = abs_path.replace(base_dir, '')
            output_lines = []
            for hash_algo in algo_list:
                try:
                    chksum_helper = _algo_map.get(hash_algo, _algo_map["md5"])
                    checksum = chksum_helper(abs_path, max_chunks=max_chunks)
                    line = "%s %s\n" % (checksum, relative_path)
                    logger.info("%s %s of %s: %s" %
                                (op_name, hash_algo, abs_path, checksum))
                    output_lines.append(line)
                except Exception, exc:
                    output_objects.append({
                        'object_type':
                        'error_text',
                        'text':
                        "%s: '%s': %s" % (op_name, relative_path, exc)
                    })
                    logger.error("%s: failed on '%s': %s" %
                                 (op_name, relative_path, exc))
                    status = returnvalues.SYSTEM_ERROR
                    continue
            entry = {'object_type': 'file_output', 'lines': output_lines}
            output_objects.append(entry)
            all_lines += output_lines
Example #9
0
def main(client_id, user_arguments_dict):
    """Main function used by front end"""

    (configuration, logger, output_objects, op_name) = \
        initialize_main_variables(client_id, op_header=False)
    client_dir = client_id_dir(client_id)
    defaults = signature()[1]
    (validate_status, accepted) = validate_input_and_cert(
        user_arguments_dict,
        defaults,
        output_objects,
        client_id,
        configuration,
        allow_rejects=False,
        )
    if not validate_status:
        return (accepted, returnvalues.CLIENT_ERROR)

    flags = ''.join(accepted['flags'])
    dst = accepted['dst'][-1].lstrip(os.sep)
    pattern_list = accepted['src']

    if not safe_handler(configuration, 'post', op_name, client_id,
                        get_csrf_limit(configuration), accepted):
        output_objects.append(
            {'object_type': 'error_text', 'text': '''Only accepting
CSRF-filtered POST requests to prevent unintended updates'''
             })
        return (output_objects, returnvalues.CLIENT_ERROR)

    # Please note that base_dir must end in slash to avoid access to other
    # user dirs when own name is a prefix of another user name

    base_dir = os.path.abspath(os.path.join(configuration.user_home,
                               client_dir)) + os.sep

    title_entry = find_entry(output_objects, 'title')
    title_entry['text'] = 'Zip/tar archive extractor'
    output_objects.append({'object_type': 'header', 'text'
                          : 'Zip/tar archive extractor'})

    if verbose(flags):
        for flag in flags:
            output_objects.append({'object_type': 'text', 'text'
                                  : '%s using flag: %s' % (op_name,
                                  flag)})

    if 'h' in flags:
        usage(output_objects)

    # IMPORTANT: path must be expanded to abs for proper chrooting
    abs_dest = os.path.abspath(os.path.join(base_dir, dst))
    logger.info('unpack in %s' % abs_dest)

    # Don't use real dest in output as it may expose underlying
    # fs layout.

    relative_dest = abs_dest.replace(base_dir, '')
    if not valid_user_path(configuration, abs_dest, base_dir, True):

        # out of bounds

        logger.error('%s tried to %s restricted path %s ! (%s)'
                       % (client_id, op_name, abs_dest, dst))
        output_objects.append(
            {'object_type': 'error_text', 'text'
             : "Invalid path! (%s expands to an illegal path)" % dst})
        return (output_objects, returnvalues.CLIENT_ERROR)

    if not check_write_access(abs_dest, parent_dir=True):
        logger.warning('%s called without write access: %s' % \
                       (op_name, abs_dest))
        output_objects.append(
            {'object_type': 'error_text', 'text':
             'cannot unpack to "%s": inside a read-only location!' % \
             relative_dest})
        return (output_objects, returnvalues.CLIENT_ERROR)

    status = returnvalues.OK

    for pattern in pattern_list:

        # Check directory traversal attempts before actual handling to avoid
        # leaking information about file system layout while allowing
        # consistent error messages

        unfiltered_match = glob.glob(base_dir + pattern)
        match = []
        for server_path in unfiltered_match:
            # IMPORTANT: path must be expanded to abs for proper chrooting
            abs_path = os.path.abspath(server_path)
            if not valid_user_path(configuration, abs_path, base_dir, True):
                logger.warning('%s tried to %s restricted path %s ! (%s)'
                               % (client_id, op_name, abs_path, pattern))
                continue
            match.append(abs_path)

        # Now actually treat list of allowed matchings and notify if no
        # (allowed) match

        if not match:
            output_objects.append({'object_type': 'file_not_found',
                                  'name': pattern})
            status = returnvalues.FILE_NOT_FOUND

        for abs_path in match:
            relative_path = abs_path.replace(base_dir, '')
            if verbose(flags):
                output_objects.append({'object_type': 'file', 'name'
                                       : relative_path})
            (unpack_status, msg) = unpack_archive(configuration,
                                                  client_id,
                                                  relative_path,
                                                  relative_dest)
            if not unpack_status:
                output_objects.append({'object_type': 'error_text',
                                       'text': 'Error: %s' % msg})
                status = returnvalues.CLIENT_ERROR
                continue
            output_objects.append(
                {'object_type': 'text', 'text'
                 : 'The zip/tar archive %s was unpacked in %s'
                 % (relative_path, relative_dest)})

    return (output_objects, status)
Example #10
0
def main(client_id, user_arguments_dict, environ=None):
    """Main function used by front end"""

    if environ is None:
        environ = os.environ

    (configuration, logger, output_objects, op_name) = \
        initialize_main_variables(client_id)
    client_dir = client_id_dir(client_id)
    status = returnvalues.OK
    defaults = signature()[1]
    (validate_status, accepted) = validate_input_and_cert(
        user_arguments_dict,
        defaults,
        output_objects,
        client_id,
        configuration,
        allow_rejects=False,
    )
    if not validate_status:
        return (accepted, returnvalues.CLIENT_ERROR)

    flags = ''.join(accepted['flags'])
    src_list = accepted['src']
    dst = accepted['dst'][-1]

    if not safe_handler(configuration, 'post', op_name, client_id,
                        get_csrf_limit(configuration), accepted):
        output_objects.append({
            'object_type':
            'error_text',
            'text':
            '''Only accepting
CSRF-filtered POST requests to prevent unintended updates'''
        })
        return (output_objects, returnvalues.CLIENT_ERROR)

    # Please note that base_dir must end in slash to avoid access to other
    # user dirs when own name is a prefix of another user name

    base_dir = os.path.abspath(
        os.path.join(configuration.user_home, client_dir)) + os.sep

    status = returnvalues.OK

    abs_dest = base_dir + dst
    dst_list = glob.glob(abs_dest)
    if not dst_list:

        # New destination?

        if not glob.glob(os.path.dirname(abs_dest)):
            output_objects.append({
                'object_type': 'error_text',
                'text': 'Illegal dst path provided!'
            })
            return (output_objects, returnvalues.CLIENT_ERROR)
        else:
            dst_list = [abs_dest]

    # Use last match in case of multiple matches

    dest = dst_list[-1]
    if len(dst_list) > 1:
        output_objects.append({
            'object_type':
            'warning',
            'text':
            'dst (%s) matches multiple targets - using last: %s' % (dst, dest)
        })

    # IMPORTANT: path must be expanded to abs for proper chrooting
    abs_dest = os.path.abspath(dest)

    # Don't use abs_path in output as it may expose underlying
    # fs layout.

    relative_dest = abs_dest.replace(base_dir, '')
    if not valid_user_path(configuration, abs_dest, base_dir, True):
        logger.warning('%s tried to %s to restricted path %s ! (%s)' %
                       (client_id, op_name, abs_dest, dst))
        output_objects.append({
            'object_type':
            'error_text',
            'text':
            "Invalid path! (%s expands to an illegal path)" % dst
        })
        return (output_objects, returnvalues.CLIENT_ERROR)
    if not check_write_access(abs_dest, parent_dir=True):
        logger.warning('%s called without write access: %s' %
                       (op_name, abs_dest))
        output_objects.append({
            'object_type':
            'error_text',
            'text':
            'cannot move to "%s": inside a read-only location!' % relative_dest
        })
        return (output_objects, returnvalues.CLIENT_ERROR)

    for pattern in src_list:
        unfiltered_match = glob.glob(base_dir + pattern)
        match = []
        for server_path in unfiltered_match:
            # IMPORTANT: path must be expanded to abs for proper chrooting
            abs_path = os.path.abspath(server_path)
            if not valid_user_path(configuration, abs_path, base_dir, True):
                logger.warning('%s tried to %s restricted path %s ! (%s)' %
                               (client_id, op_name, abs_path, pattern))
                continue
            match.append(abs_path)

        # Now actually treat list of allowed matchings and notify if no
        # (allowed) match

        if not match:
            output_objects.append({
                'object_type':
                'error_text',
                'text':
                '%s: no such file or directory! %s' % (op_name, pattern)
            })
            status = returnvalues.CLIENT_ERROR

        for abs_path in match:
            relative_path = abs_path.replace(base_dir, '')
            if verbose(flags):
                output_objects.append({
                    'object_type': 'file',
                    'name': relative_path
                })

            # Generally refuse handling symlinks including root vgrid shares
            if os.path.islink(abs_path):
                output_objects.append({
                    'object_type':
                    'warning',
                    'text':
                    """You're not allowed to
move entire special folders like %s shared folders!""" %
                    configuration.site_vgrid_label
                })
                status = returnvalues.CLIENT_ERROR
                continue
            # Additionally refuse operations on inherited subvgrid share roots
            elif in_vgrid_share(configuration, abs_path) == relative_path:
                output_objects.append({
                    'object_type':
                    'warning',
                    'text':
                    """You're not allowed to
move entire %s shared folders!""" % configuration.site_vgrid_label
                })
                status = returnvalues.CLIENT_ERROR
                continue
            elif os.path.realpath(abs_path) == os.path.realpath(base_dir):
                logger.error("%s: refusing move home dir: %s" %
                             (op_name, abs_path))
                output_objects.append({
                    'object_type':
                    'warning',
                    'text':
                    "You're not allowed to move your entire home directory!"
                })
                status = returnvalues.CLIENT_ERROR
                continue

            if not check_write_access(abs_path):
                logger.warning('%s called without write access: %s' %
                               (op_name, abs_path))
                output_objects.append({
                    'object_type':
                    'error_text',
                    'text':
                    'cannot move "%s": inside a read-only location!' % pattern
                })
                status = returnvalues.CLIENT_ERROR
                continue

            # If destination is a directory the src should be moved in there
            # Move with existing directory as target replaces the directory!

            abs_target = abs_dest
            if os.path.isdir(abs_target):
                if os.path.samefile(abs_target, abs_path):
                    output_objects.append({
                        'object_type':
                        'warning',
                        'text':
                        "Cannot move '%s' to a subdirectory of itself!" %
                        relative_path
                    })
                    status = returnvalues.CLIENT_ERROR
                    continue
                abs_target = os.path.join(abs_target,
                                          os.path.basename(abs_path))

            try:
                gdp_iolog(configuration, client_id, environ['REMOTE_ADDR'],
                          'moved', [relative_path, relative_dest])
                shutil.move(abs_path, abs_target)
                logger.info('%s %s %s done' % (op_name, abs_path, abs_target))
            except Exception, exc:
                if not isinstance(exc, GDPIOLogError):
                    gdp_iolog(configuration,
                              client_id,
                              environ['REMOTE_ADDR'],
                              'moved', [relative_path, relative_dest],
                              failed=True,
                              details=exc)
                output_objects.append({
                    'object_type':
                    'error_text',
                    'text':
                    "%s: '%s': %s" % (op_name, relative_path, exc)
                })
                logger.error("%s: failed on '%s': %s" %
                             (op_name, relative_path, exc))

                status = returnvalues.SYSTEM_ERROR
                continue