示例#1
0
def __cron_log(configuration, client_id, msg, level="info"):
    """Wrapper to send a single msg to user cron log file"""

    client_dir = client_id_dir(client_id)
    log_dir_path = os.path.join(configuration.user_home, client_dir,
                                cron_output_dir)
    log_path = os.path.join(log_dir_path, cron_log_name)
    if not os.path.exists(log_dir_path):
        try:
            os.makedirs(log_dir_path)
        except:
            pass
    cron_logger = logging.getLogger('cron')
    cron_logger.setLevel(logging.INFO)
    handler = logging.handlers.RotatingFileHandler(
        log_path, maxBytes=cron_log_size, backupCount=cron_log_cnt - 1)
    formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
    handler.setFormatter(formatter)
    cron_logger.addHandler(handler)
    if level == 'error':
        cron_logger.error(msg)
    elif level == 'warning':
        cron_logger.warning(msg)
    else:
        cron_logger.info(msg)
    handler.flush()
    handler.close()
    cron_logger.removeHandler(handler)
示例#2
0
def remove2fa(configuration, user_id, verbose, force=False):
    """Removes two factor secrets and settings for client_id
    NOTE: Active sessions remain until logout"""
    _logger = configuration.logger
    if verbose:
        print("Removing 2FA setup for %r" % user_id)
    allow_missing = False
    if force:
        allow_missing = True
    client_dir = client_id_dir(user_id)
    settings_dir = os.path.join(configuration.user_settings, client_dir)

    key_path = os.path.join(settings_dir, twofactor_key_name)
    if verbose:
        print("Removing key file: %s" % key_path)
    status = delete_file(key_path, _logger, allow_missing=allow_missing)
    if not status and not force:
        return status

    interval_path = os.path.join(settings_dir, twofactor_interval_name)
    if verbose:
        print("Removing interval file: %s" % interval_path)
    status = delete_file(interval_path, _logger, allow_missing=True)
    if not status and not force:
        return status

    twofactor_settings_path = os.path.join(settings_dir, twofactor_filename)
    if verbose:
        print("Removing twofactor file: %s" % twofactor_settings_path)
    status = delete_file(twofactor_settings_path,
                         _logger,
                         allow_missing=allow_missing)

    return status
示例#3
0
def manage_pending_peers(configuration, client_id, action, change_list):
    """Helper to manage changes to pending peers list of client_id"""
    _logger = configuration.logger
    client_dir = client_id_dir(client_id)
    pending_peers_path = os.path.join(configuration.user_settings, client_dir,
                                      pending_peers_filename)
    try:
        pending_peers = load(pending_peers_path)
    except Exception as exc:
        if os.path.exists(pending_peers_path):
            _logger.warning("could not load pending peers from %s: %s" %
                            (pending_peers_path, exc))
        pending_peers = []
    change_dict = dict(change_list)
    # NOTE: always remove old first to replace any existing and move them last
    pending_peers = [(i, j)
                     for (i, j) in pending_peers if not i in change_dict]
    if action == "add":
        pending_peers += change_list
    elif action == "remove":
        pass
    else:
        _logger.error(
            "unsupported action in manage pending peers: %s" % action)
        return False
    try:
        dump(pending_peers, pending_peers_path)
        return True
    except Exception as exc:
        _logger.warning("could not save pending peers to %s: %s" %
                        (pending_peers_path, exc))
        return False
示例#4
0
def check_account_accessible(configuration,
                             username,
                             proto,
                             environ=None,
                             io_login=True,
                             expand_alias=True):
    """Check username account status and expire field in cache and user DB
    if needed and return a boolean to tell if account is accessible or not.
    The proto argument is used to detect if only users are allowed or if e.g.
    sharelinks, jobs or jupyter mounts should also be checked.
    The optional environ overrides the environment dict which is otherwise
    taken from os.environ and the io_login is a boolean to decide if
    configuration.site_io_account_expire should be honored or if expire should
    be enforced only if configuration.user_openid_enforce_expire is set. The
    optional expand_alias can be used to force expansion of username from an
    alias to the full DN so that e.g. the user email address alias can be
    provided and automatically looked up. This is particularly convenient when
    called from PAM for SFTP.
    """
    _logger = configuration.logger
    if not environ:
        environ = os.environ

    # We might end up here from IO daemon logins with user, sharelink, job and
    # jupyter mounts. We should let all but actual user logins pass for now.
    # TODO: consider checking underlying user for other types eventually?
    if detect_special_login(configuration, username, proto):
        _logger.debug("found %s as special %s login" % (username, proto))
        return True

    # NOTE: now we know username must be an ordinary user to check
    client_id = username
    if configuration.site_enable_gdp:
        client_id = get_base_client_id(configuration,
                                       client_id,
                                       expand_oid_alias=expand_alias)
    elif expand_alias:
        # Use client_id_dir to make it work even if already expanded
        home_dir = os.path.join(configuration.user_home,
                                client_id_dir(client_id))
        real_home = os.path.realpath(home_dir)
        real_id = os.path.basename(real_home)
        client_id = client_dir_id(real_id)

    (account_accessible, account_status,
     _) = check_account_status(configuration, client_id)
    if not account_accessible:
        _logger.debug("%s account %s" % (client_id, account_status))
        return False
    if io_login and not configuration.site_io_account_expire:
        _logger.debug("%s account active and no IO expire" % client_id)
        return True
    if not io_login and not configuration.user_openid_enforce_expire:
        _logger.debug("%s account active and no OpenID expire" % client_id)
        return True
    (pending_expire, account_expire,
     _) = check_account_expire(configuration, client_id, environ)
    return pending_expire
示例#5
0
def get_account_expire_cache(configuration, client_id):
    """Check if account with client_id has an expire mark in the cache and
    if so return the timestamp associated with it.
    """
    _logger = configuration.logger
    if not client_id:
        _logger.error("invalid client ID: %s" % client_id)
        return False
    client_dir = client_id_dir(client_id)
    base_dir = os.path.join(configuration.mig_system_run, expire_marks_dir)
    return get_filemark(configuration, base_dir, client_dir)
示例#6
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)

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

    status = returnvalues.OK

    title_entry = find_entry(output_objects, 'title')
    title_entry['text'] = 'Job Manager'
    user_settings = title_entry.get('user_settings', {})
    title_entry['style'] = css_tmpl(configuration, user_settings)
    csrf_map = {}
    method = 'post'
    limit = get_csrf_limit(configuration)
    for target_op in csrf_backends:
        csrf_map[target_op] = make_csrf_token(configuration, method, target_op,
                                              client_id, limit)
    (add_import, add_init, add_ready) = js_tmpl_parts(csrf_map)
    title_entry['script']['advanced'] += add_import
    title_entry['script']['init'] += add_init
    title_entry['script']['ready'] += add_ready

    output_objects.append({'object_type': 'header', 'text': 'Job Manager'})
    output_objects.append({
        'object_type': 'table_pager',
        'entry_name': 'jobs',
        'default_entries': default_pager_entries,
        'form_append': pager_append()
    })
    output_objects.append({'object_type': 'html_form', 'text': html_post()})

    return (output_objects, status)
示例#7
0
def migrate_job(config, job, peer):
    protocol = 'https'
    port = ''

    server = peer['fqdn']

    # Remove schedule hint from job before migration

    del job['SCHEDULE_HINT']

    # Make sure legacy jobs don't fail

    if 'MIGRATE_COUNT' not in job:
        job['MIGRATE_COUNT'] = str(0)

    # Add or increment migration counter

    migrate_count = int(job['MIGRATE_COUNT']) + 1
    job['MIGRATE_COUNT'] = str(migrate_count)

    # TODO: only upload if job is not already replicated at
    # remote server
    # TMP!

    steal_job = False

    if not steal_job:

        # upload pickled job to server

        client_dir = client_id_dir(job['USER_CERT'])
        mrsl_filename = config.mrsl_files_dir + client_dir + '/'\
             + job['JOB_ID'] + '.mRSL'
        result = pickle(job, mrsl_filename, config.logger)
        if not result:
            config.logger.error('Aborting migration of job %s (%s)',
                                job['JOB_ID'], result)
            return False

        dest = mrsl_filename

        # TMP!
        # upload_reply = put_data(config, mrsl_filename, protocol, server, port, dest)

        config.logger.warning(
            'Actual migration disabled until fully supported')
        upload_reply = (-1, 'Actual migration disabled until fully supported')
        if upload_reply[0] != http_success:
            return False

    # migration_msg = ""
    # migration_reply = put_data(config, protocol, server, port, migration_msg)

    return True
示例#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, op_header=False, op_title=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)
    job_id_list = accepted['job_id']
    external_dict = mrslkeywords.get_keywords_dict(configuration)

    if not configuration.site_enable_jobs:
        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.mrsl_files_dir,
                        client_dir)) + os.sep

    status = returnvalues.OK
    for job_id in job_id_list:

        # job = Job()

        filepath = os.path.join(base_dir, job_id)
        filepath += '.mRSL'

        (new_job_obj_status, new_job_obj) = \
            create_job_object_from_pickled_mrsl(filepath, logger,
                external_dict)
        if not new_job_obj_status:
            output_objects.append({'object_type': 'error_text', 'text'
                                  : new_job_obj})
            status = returnvalues.CLIENT_ERROR
        else:

            # return new_job_obj

            output_objects.append({'object_type': 'jobobj', 'jobobj'
                                  : new_job_obj})
    return (output_objects, status)
示例#9
0
def handle_proxy(proxy_string, client_id, config):
    """If ARC-enabled server: store a proxy certificate.
       Arguments: proxy_string - text  extracted from given upload
                  client_id  - DN for user just being created
                  config     - global configuration
    """

    output = []
    client_dir = client_id_dir(client_id)
    proxy_dir = os.path.join(config.user_home, client_dir)
    proxy_path = os.path.join(config.user_home, client_dir,
                              arcwrapper.Ui.proxy_name)

    if not config.arc_clusters:
        output.append({'object_type': 'error_text',
                       'text': 'No ARC support!'})
        return output

    # store the file

    try:
        write_file(proxy_string, proxy_path, config.logger)
        os.chmod(proxy_path, 0o600)
    except Exception as exc:
        output.append({'object_type': 'error_text',
                       'text': 'Proxy file could not be written (%s)!'
                       % str(exc).replace(proxy_dir, '')})
        return output

    # provide information about the uploaded proxy

    try:
        session_ui = arcwrapper.Ui(proxy_dir)
        proxy = session_ui.getProxy()
        if proxy.IsExpired():

            # can rarely happen, constructor will throw exception

            output.append({'object_type': 'warning',
                           'text': 'Proxy certificate is expired.'})
        else:
            output.append({'object_type': 'text', 'text': 'Proxy for %s'
                           % proxy.GetIdentitySN()})
            output.append({'object_type': 'text',
                           'text': 'Proxy certificate will expire on %s (in %s sec.)'
                           % (proxy.Expires(), proxy.getTimeleft())})
    except arcwrapper.NoProxyError as err:

        output.append({'object_type': 'warning',
                       'text': 'No proxy certificate to load: %s'
                       % err.what()})
    return output
示例#10
0
def get_accepted_peers(configuration, client_id):
    """Helper to get the list of peers accepted by client_id"""
    _logger = configuration.logger
    client_dir = client_id_dir(client_id)
    peers_path = os.path.join(configuration.user_settings, client_dir,
                              peers_filename)
    try:
        accepted_peers = load(peers_path)
    except Exception as exc:
        if os.path.exists(peers_path):
            _logger.warning("could not load peers from %s: %s" %
                            (peers_path, exc))
        accepted_peers = {}
    return accepted_peers
示例#11
0
def load_crontab(client_id, configuration, allow_missing=True):
    """Load entries from plain user crontab file"""
    _logger = configuration.logger
    client_dir = client_id_dir(client_id)
    crontab_path = os.path.join(configuration.user_settings, client_dir,
                                crontab_name)
    try:
        crontab_fd = open(crontab_path, "rb")
        crontab_contents = crontab_fd.read()
        crontab_fd.close()
    except Exception as exc:
        if not allow_missing:
            _logger.error('failed reading %s crontab file: %s' % (client_id,
                                                                  exc))
        crontab_contents = ''
    return crontab_contents
示例#12
0
def load_atjobs(client_id, configuration, allow_missing=True):
    """Load entries from plain user atjobs file"""
    _logger = configuration.logger
    client_dir = client_id_dir(client_id)
    atjobs_path = os.path.join(configuration.user_settings, client_dir,
                               atjobs_name)
    try:
        atjobs_fd = open(atjobs_path, "rb")
        atjobs_contents = atjobs_fd.read()
        atjobs_fd.close()
    except Exception as exc:
        if not allow_missing:
            _logger.error('failed reading %s atjobs file: %s' % (client_id,
                                                                 exc))
        atjobs_contents = ''
    return atjobs_contents
示例#13
0
def parse_and_save_atjobs(atjobs, client_id, configuration):
    """Validate and write the atjobs for client_id"""
    client_dir = client_id_dir(client_id)
    atjobs_path = os.path.join(configuration.user_settings, client_dir,
                               atjobs_name)
    status, msg = True, ''
    atjobs_entries = parse_atjobs_contents(configuration, client_id,
                                           atjobs.splitlines())
    try:
        atjobs_fd = open(atjobs_path, "wb")
        # TODO: filter out broken lines before write?
        atjobs_fd.write(atjobs)
        atjobs_fd.close()
        msg = "Found and saved %d valid atjobs entries" % len(atjobs_entries)
    except Exception as exc:
        status = False
        msg = 'ERROR: writing %s atjobs file: %s' % (client_id, exc)
    return (status, msg)
示例#14
0
def parse_and_save_crontab(crontab, client_id, configuration):
    """Validate and write the crontab for client_id"""
    client_dir = client_id_dir(client_id)
    crontab_path = os.path.join(configuration.user_settings, client_dir,
                                crontab_name)
    status, msg = True, ''
    crontab_entries = parse_crontab_contents(configuration, client_id,
                                             crontab.splitlines())
    try:
        crontab_fd = open(crontab_path, "wb")
        # TODO: filter out broken lines before write?
        crontab_fd.write(crontab)
        crontab_fd.close()
        msg = "Found and saved %d valid crontab entries" % len(crontab_entries)
    except Exception as exc:
        status = False
        msg = 'ERROR: writing %s crontab file: %s' % (client_id, exc)
    return (status, msg)
示例#15
0
def get_account_status_cache(configuration, client_id):
    """Check if account with client_id has an status mark in the cache and
    if so return the timestamp associated with it.
    """
    _logger = configuration.logger
    if not client_id:
        _logger.error("invalid client ID: %s" % client_id)
        return False
    client_dir = client_id_dir(client_id)
    base_dir = os.path.join(configuration.mig_system_run, status_marks_dir)
    # NOTE: translate status integer encoded as timestamp back to string
    status_index = get_filemark(configuration, base_dir, client_dir)
    if status_index is None:
        return status_index
    if status_index < 0 or status_index >= len(valid_account_status):
        _logger.error("invalid cached client status for %s: %s" %
                      (client_id, status_index))
        return None
    return valid_account_status[int(status_index)]
示例#16
0
def reset_twofactor_key(client_id, configuration, seed=None, interval=None):
    """Reset 2FA secret key and write to user settings file in scrambled form.
    Return the new secret key on unscrambled base32 form.
    """
    _logger = configuration.logger
    if configuration.site_enable_gdp:
        client_id = get_base_client_id(configuration,
                                       client_id,
                                       expand_oid_alias=False)
    client_dir = client_id_dir(client_id)
    key_path = os.path.join(configuration.user_settings, client_dir,
                            twofactor_key_name)
    try:
        if pyotp is None:
            raise Exception("The pyotp module is missing and required for 2FA")
        if not seed:
            b32_key = pyotp.random_base32(length=twofactor_key_bytes)
        else:
            b32_key = seed
        # NOTE: pyotp.random_base32 returns unicode
        #       which causes trouble with WSGI
        b32_key = force_utf8(b32_key)
        scrambled = scramble_password(configuration.site_password_salt,
                                      b32_key)
        key_fd = open(key_path, 'w')
        key_fd.write(scrambled)
        key_fd.close()

        # Reset interval

        interval_path = os.path.join(configuration.user_settings, client_dir,
                                     twofactor_interval_name)
        delete_file(interval_path, _logger, allow_missing=True)
        if interval:
            i_fd = open(interval_path, 'w')
            i_fd.write("%d" % interval)
            i_fd.close()
    except Exception as exc:
        _logger.error("failed in reset 2FA key: %s" % exc)
        return False

    return b32_key
示例#17
0
def read_cron_log(configuration, client_id, flags):
    """Read-in saved cron logs for crontab. We read in all rotated logs"""

    client_dir = client_id_dir(client_id)
    log_content = ''
    for i in xrange(cron_log_cnt - 1, -1, -1):
        log_name = cron_log_name
        if i > 0:
            log_name += '.%d' % i
        log_path = os.path.join(configuration.user_home, client_dir,
                                cron_output_dir, log_name)
        configuration.logger.debug('read from %s' % log_path)
        try:
            log_fd = open(log_path)
            log_content += log_fd.read()
            configuration.logger.debug('read in log lines:\n%s' % log_content)
            log_fd.close()
        except IOError:
            pass
    return log_content
示例#18
0
def load_twofactor_interval(client_id, configuration):
    """Load 2FA token interval"""
    _logger = configuration.logger
    if configuration.site_enable_gdp:
        client_id = get_base_client_id(configuration,
                                       client_id,
                                       expand_oid_alias=False)
    client_dir = client_id_dir(client_id)
    interval_path = os.path.join(configuration.user_settings, client_dir,
                                 twofactor_interval_name)
    result = None
    if os.path.isfile(interval_path):
        i_fd = open(interval_path)
        interval = i_fd.read().strip()
        i_fd.close()
        try:
            result = int(interval)
        except Exception as exc:
            result = None
            _logger.error("Failed to read twofactor interval: %s" % exc)

    return result
示例#19
0
def unpack_archive(
    configuration,
    client_id,
    src,
    dst,
):
    """Inside the user home of client_id: unpack the src zip or tar
    archive into the dst dir. Both src and dst are expected to be relative
    paths.
    Please note that src and dst should be checked for illegal directory
    traversal attempts *before* getting here.
    """
    client_dir = client_id_dir(client_id)

    # 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
    real_src = os.path.join(base_dir, src.lstrip(os.sep))
    return handle_package_upload(real_src, src, client_id,
                                 configuration, False, dst)
示例#20
0
def update_account_expire_cache(configuration, user_dict):
    """Create or update expire mark for account with given user_dict if it
    contains the expire field.
    """
    _logger = configuration.logger
    if not isinstance(user_dict, dict):
        _logger.error("invalid user_dict: %s" % user_dict)
        return False
    client_id = user_dict.get('distinguished_name', None)
    if not client_id:
        _logger.error("no client ID set for user: %s" % user_dict)
        return False
    expire = user_dict.get('expire', None)
    if not expire:
        _logger.info("no expire set for user: %s" % user_dict)
        return True
    elif isinstance(expire, basestring):
        _logger.warning("found string expire value for user: %s" % user_dict)
        return False
    client_dir = client_id_dir(client_id)
    base_dir = os.path.join(configuration.mig_system_run, expire_marks_dir)
    return update_filemark(configuration, base_dir, client_dir, expire)
示例#21
0
def load_twofactor_key(client_id, configuration, allow_missing=True):
    """Load 2FA secret key on scrambled form from user settings file and
    return the unscrambled form.
    """
    _logger = configuration.logger
    if configuration.site_enable_gdp:
        client_id = get_base_client_id(configuration,
                                       client_id,
                                       expand_oid_alias=False)
    client_dir = client_id_dir(client_id)
    key_path = os.path.join(configuration.user_settings, client_dir,
                            twofactor_key_name)
    b32_key = None
    try:
        pw_fd = open(key_path)
        scrambled = pw_fd.read().strip()
        pw_fd.close()
        b32_key = unscramble_password(configuration.site_password_salt,
                                      scrambled)
    except Exception as exc:
        if not allow_missing:
            _logger.error("load 2FA key failed: %s" % exc)
    return b32_key
示例#22
0
def update_account_status_cache(configuration, user_dict):
    """Create or update status mark for account with given user_dict if it
    contains the status field.
    """
    _logger = configuration.logger
    if not isinstance(user_dict, dict):
        _logger.error("invalid user_dict: %s" % user_dict)
        return False
    client_id = user_dict.get('distinguished_name', None)
    if not client_id:
        _logger.error("no client ID set for user: %s" % user_dict)
        return False
    # NOTE: translate status strings to integers here to encode as timestamp
    status_key = user_dict.get('status', None)
    if not status_key:
        _logger.info("no status set for user: %s" % user_dict)
        return True
    if not status_key in valid_account_status:
        _logger.error("invalid account status for user: %s" % user_dict)
        return False
    status = valid_account_status.index(status_key)
    client_dir = client_id_dir(client_id)
    base_dir = os.path.join(configuration.mig_system_run, status_marks_dir)
    return update_filemark(configuration, base_dir, client_dir, status)
示例#23
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,
        # NOTE: path can use wildcards
        typecheck_overrides={'path': valid_path_pattern},
    )
    if not validate_status:
        return (accepted, returnvalues.CLIENT_ERROR)

    flags = ''.join(accepted['flags'])
    patterns = accepted['path']
    search = accepted['pattern'][-1]

    # 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)})

    for pattern in patterns:

        # 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 = []
            try:
                matching = pattern_match_file(search, abs_path)
                for line in matching:
                    output_lines.append(line)
            except Exception as 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,
                     'wrap_binary': binary(flags),
                     'wrap_targets': ['lines']}
            if verbose(flags):
                entry['path'] = relative_path
            output_objects.append(entry)

    return (output_objects, status)
示例#24
0
def create_arc_job(
    job,
    configuration,
    logger,
):
    """Analog to create_job_script for ARC jobs:
    Creates symLinks for receiving result files, translates job dict to ARC
    xrsl, and stores resulting job script (xrsl + sh script) for submitting.

    We do _not_ create a separate job_dict with copies and SESSIONID inside,
    as opposed to create_job_script, all we need is the link from 
    webserver_home / sessionID into the user's home directory 
    ("job_output/job['JOB_ID']" is added to the result upload URLs in the 
    translation). 

    Returns message (ARC job ID if no error) and sessionid (None if error)
    """

    if not configuration.arc_clusters:
        return (None, 'No ARC support!')
    if not job['JOBTYPE'] == 'arc':
        return (None, 'Error. This is not an ARC job')

    # Deep copy job for local changes
    job_dict = deepcopy(job)
    # Finally expand reserved job variables like +JOBID+ and +JOBNAME+
    job_dict = expand_variables(job_dict)
    # ... no more changes to job_dict from here on
    client_id = str(job_dict['USER_CERT'])

    # we do not want to see empty jobs here. Test as done in create_job_script.
    if client_id == configuration.empty_job_name:
        return (None, 'Error. empty job for ARC?')

    # generate random session ID:
    sessionid = hexlify(open('/dev/urandom').read(session_id_bytes))
    logger.debug('session ID (for creating links): %s' % sessionid)

    client_dir = client_id_dir(client_id)

    # make symbolic links inside webserver_home:
    #
    # we need: link to owner's dir. to receive results,
    #          job mRSL inside sessid_to_mrsl_link_home
    linklist = [(configuration.user_home + client_dir,
                 configuration.webserver_home + sessionid),
                (configuration.mrsl_files_dir + client_dir + '/' +
                 str(job_dict['JOB_ID']) + '.mRSL',
                 configuration.sessid_to_mrsl_link_home + sessionid + '.mRSL')]

    for (dest, loc) in linklist:
        make_symlink(dest, loc, logger)

    # the translation generates an xRSL object which specifies to execute
    # a shell script with script_name. If sessionid != None, results will
    # be uploaded to sid_redirect/sessionid/job_output/job_id

    try:
        (xrsl, script, script_name) = mrsltoxrsl.translate(job_dict, sessionid)
        logger.debug('translated to xRSL: %s' % xrsl)
        logger.debug('script:\n %s' % script)

    except Exception as err:
        # error during translation, pass a message
        logger.error('Error during xRSL translation: %s' % err.__str__())
        return (None, err.__str__())

        # we submit directly from here (the other version above does
        # copyFileToResource and gen_job_script generates all files)

    # we have to put the generated script somewhere..., and submit from there.
    # inputfiles are given by the user as relative paths from his home,
    # so we should use that location (and clean up afterwards).

    # write script (to user home)
    user_home = os.path.join(configuration.user_home, client_dir)
    script_path = os.path.abspath(os.path.join(user_home, script_name))
    write_file(script, script_path, logger)

    os.chdir(user_home)

    try:
        logger.debug('submitting job to ARC')
        session = arcwrapper.Ui(user_home)
        arc_job_ids = session.submit(xrsl)

        # if no exception occurred, we are done:

        job_dict['ARCID'] = arc_job_ids[0]
        job_dict['SESSIONID'] = sessionid

        msg = 'OK'
        result = job_dict

    # when errors occurred, pass a message to the caller.
    except arcwrapper.ARCWrapperError as err:
        msg = err.what()
        result = None  # unsuccessful
    except arcwrapper.NoProxyError as err:
        msg = 'No Proxy found: %s' % err.what()
        result = None  # unsuccessful
    except Exception as err:
        msg = err.__str__()
        result = None  # unsuccessful

    # always remove the generated script
    os.remove(script_name)
    # and remove the created links immediately if failed
    if not result:
        for (_, link) in linklist:
            os.remove(link)
        logger.error('Unsuccessful ARC job submission: %s' % msg)
    else:
        logger.debug('submitted to ARC as job %s' % msg)
    return (result, msg)
示例#25
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)

    patterns = accepted['job_id']

    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:
        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.mrsl_files_dir,
                        client_dir)) + os.sep

    status = returnvalues.OK
    filelist = []
    for pattern in patterns:
        pattern = pattern.strip()

        # Backward compatibility - all_jobs keyword should match all jobs

        if pattern == all_jobs:
            pattern = '*'

        # 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 + '.mRSL')
        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

            # Insert valid job files in filelist for later treatment

            match.append(abs_path)

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

        if not match:
            output_objects.append({
                'object_type':
                'error_text',
                'text':
                '%s: You do not have any matching job IDs!' % pattern
            })
            status = returnvalues.CLIENT_ERROR
        else:
            filelist += match

    # job feasibility is hard on the server, limit

    if len(filelist) > 100:
        output_objects.append({
            'object_type':
            'error_text',
            'text':
            'Too many matching jobs (%s)!' % len(filelist)
        })
        return (output_objects, returnvalues.CLIENT_ERROR)

    checkcondjobs = []

    for filepath in filelist:

        # Extract job_id from filepath (replace doesn't modify filepath)

        mrsl_file = filepath.replace(base_dir, '')
        job_id = mrsl_file.replace('.mRSL', '')

        checkcondjob = {'object_type': 'checkcondjob', 'job_id': job_id}

        dict = unpickle(filepath, logger)
        if not dict:
            checkcondjob['message'] = \
                                    ('The file containing the information ' \
                                     'for job id %s could not be opened! ' \
                                     'You can only check feasibility of ' \
                                     'your own jobs!'
                                     ) % job_id
            checkcondjobs.append(checkcondjob)
            status = returnvalues.CLIENT_ERROR
            continue

        # Is the job status pending?

        possible_check_states = ['QUEUED', 'RETRY', 'FROZEN']
        if not dict['STATUS'] in possible_check_states:
            checkcondjob['message'] = \
                'You can only check feasibility of jobs with status: %s.'\
                 % ' or '.join(possible_check_states)
            checkcondjobs.append(checkcondjob)
            continue

        # Actually check feasibility
        feasible_res = job_feasibility(configuration, dict)
        checkcondjob.update(feasible_res)
        checkcondjobs.append(checkcondjob)

    output_objects.append({
        'object_type': 'checkcondjobs',
        'checkcondjobs': checkcondjobs
    })
    return (output_objects, status)
示例#26
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,
        # NOTE: path can use wildcards, dst and current_dir cannot
        typecheck_overrides={'path': valid_path_pattern},
    )
    if not validate_status:
        return (accepted, returnvalues.CLIENT_ERROR)

    flags = ''.join(accepted['flags'])
    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

        # NOTE: we produce output matching an invocation of:
        # du -aL --apparent-size --block-size=1 PATH [PATH ...]
        filedus = []
        summarize_output = summarize(flags)
        for abs_path in match:
            if invisible_path(abs_path):
                continue
            relative_path = abs_path.replace(base_dir, '')
            # cache accumulated sub dir sizes - du sums into parent dir size
            dir_sizes = {}
            try:
                # Assume a directory to walk
                for (root, dirs, files) in walk(abs_path,
                                                topdown=False,
                                                followlinks=True):
                    if invisible_path(root):
                        continue
                    dir_bytes = 0
                    for name in files:
                        real_file = os.path.join(root, name)
                        if invisible_path(real_file):
                            continue
                        relative_file = real_file.replace(base_dir, '')
                        size = os.path.getsize(real_file)
                        dir_bytes += size
                        if not summarize_output:
                            filedus.append({
                                'object_type': 'filedu',
                                'name': relative_file,
                                'bytes': size
                            })
                    for name in dirs:
                        real_dir = os.path.join(root, name)
                        if invisible_path(real_dir):
                            continue
                        dir_bytes += dir_sizes[real_dir]
                    relative_root = root.replace(base_dir, '')
                    dir_bytes += os.path.getsize(root)
                    dir_sizes[root] = dir_bytes
                    if root == abs_path or not summarize_output:
                        filedus.append({
                            'object_type': 'filedu',
                            'name': relative_root,
                            'bytes': dir_bytes
                        })
                if os.path.isfile(abs_path):
                    # Fall back to plain file where walk is empty
                    size = os.path.getsize(abs_path)
                    filedus.append({
                        'object_type': 'filedu',
                        'name': relative_path,
                        'bytes': size
                    })
            except Exception as 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
        if dst:
            all_lines += [
                '%(bytes)d\t\t%(name)s\n' % entry for entry in filedus
            ]
        else:
            output_objects.append({
                'object_type': 'filedus',
                'filedus': filedus
            })

    if dst and not write_file(''.join(all_lines), abs_dest, logger):
        output_objects.append({
            'object_type':
            'error_text',
            'text':
            "failed to write disk use to %s" % relative_dest
        })
        logger.error("writing disk use to %s for %s failed" %
                     (abs_dest, client_id))
        status = returnvalues.SYSTEM_ERROR

    return (output_objects, status)
示例#27
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,
        # NOTE: path cannot use wildcards here
        typecheck_overrides={},
    )

    if not validate_status:
        return (accepted, returnvalues.CLIENT_ERROR)

    status = returnvalues.OK

    chroot = ''
    if configuration.site_enable_gdp:
        chroot = get_project_from_client_id(configuration, client_id)

    all_paths = accepted['path']
    entry_path = all_paths[-1]
    title_entry = find_entry(output_objects, 'title')
    user_settings = title_entry.get('user_settings', {})
    title_entry['text'] = 'File Manager'
    title_entry['style'] = css_tmpl(configuration, user_settings)

    legacy_buttons = False
    if legacy_user_interface(configuration, user_settings):
        legacy_buttons = True
        logger.debug("enable legacy buttons")

    if configuration.site_enable_jobs and \
            'submitjob' in extract_menu(configuration, title_entry):
        enable_submit = 'true'
    else:
        enable_submit = 'false'
    csrf_map = {}
    method = 'post'
    limit = get_csrf_limit(configuration)
    for target_op in csrf_backends:
        csrf_map[target_op] = make_csrf_token(configuration, method, target_op,
                                              client_id, limit)
    (add_import, add_init,
     add_ready) = js_tmpl_parts(configuration, entry_path, enable_submit,
                                str(configuration.site_enable_preview),
                                legacy_buttons, csrf_map, chroot)
    title_entry['script']['advanced'] += add_import
    title_entry['script']['init'] += add_init
    title_entry['script']['ready'] += add_ready
    title_entry['container_class'] = 'fillwidth',

    output_objects.append({
        'object_type': 'header',
        'class': 'fileman-title',
        'text': 'File Manager'
    })
    output_objects.append({
        'object_type':
        'html_form',
        'text':
        html_tmpl(configuration, client_id, title_entry, csrf_map, chroot)
    })

    if len(all_paths) > 1:
        output_objects.append({
            'object_type': 'sectionheader',
            'text': 'All requested paths:'
        })
        for path in all_paths:
            output_objects.append({
                'object_type': 'link',
                'text': path,
                'destination': 'fileman.py?path=%s' % path
            })
            output_objects.append({'object_type': 'text', 'text': ''})

    return (output_objects, status)
示例#28
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,
        # NOTE: path cannot use wildcards here
        typecheck_overrides={},
    )
    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 as 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)
    if submitjob:
        output_objects.append({
            'object_type': 'text',
            'text': 'Submitting saved file to parser'
        })
        submitstatus = {'object_type': 'submitstatus', 'name': path}
        (new_job_status, msg, job_id) = new_job(abs_path, client_id,
                                                configuration, False, True)
        if not new_job_status:
            submitstatus['status'] = False
            submitstatus['message'] = msg
        else:
            submitstatus['status'] = True
            submitstatus['job_id'] = job_id

        output_objects.append({
            'object_type': 'submitstatuslist',
            'submitstatuslist': [submitstatus]
        })

    output_objects.append({
        'object_type': 'link',
        'destination': 'javascript:history.back()',
        'class': 'backlink iconspace',
        'title': 'Go back to previous page',
        'text': 'Back to previous page'
    })

    return (output_objects, returnvalues.OK)
示例#29
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)

    patterns = accepted['job_id']

    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:
        output_objects.append({
            'object_type':
            'error_text',
            'text':
            '''Job execution is not enabled on this system'''
        })
        return (output_objects, returnvalues.SYSTEM_ERROR)

    if not patterns:
        output_objects.append({
            'object_type': 'error_text',
            'text': 'No job_id specified!'
        })
        return (output_objects, returnvalues.NO_SUCH_JOB_ID)

    # 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.mrsl_files_dir,
                        client_dir)) + os.sep

    filelist = []
    keywords_dict = mrslkeywords.get_keywords_dict(configuration)
    for pattern in patterns:
        pattern = pattern.strip()

        # Backward compatibility - all_jobs keyword should match all jobs

        if pattern == all_jobs:
            pattern = '*'

        # 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 + '.mRSL')
        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

            # Insert valid job files in filelist for later treatment

            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: You do not have any matching job IDs!' % pattern
            })
            status = returnvalues.CLIENT_ERROR
        else:
            filelist += match

    # resubmit is hard on the server

    if len(filelist) > 100:
        output_objects.append({
            'object_type':
            'error_text',
            'text':
            'Too many matching jobs (%s)!' % len(filelist)
        })
        return (output_objects, returnvalues.CLIENT_ERROR)

    resubmitobjs = []
    status = returnvalues.OK
    for filepath in filelist:
        mrsl_file = filepath.replace(base_dir, '')
        job_id = mrsl_file.replace('.mRSL', '')

        # ("Resubmitting job with job_id: %s" % job_id)

        resubmitobj = {'object_type': 'resubmitobj', 'job_id': job_id}

        mrsl_dict = unpickle(filepath, logger)
        if not mrsl_dict:
            resubmitobj['message'] = "No such job: %s (%s)" % (job_id,
                                                               mrsl_file)
            status = returnvalues.CLIENT_ERROR
            resubmitobjs.append(resubmitobj)
            continue

        resubmit_items = keywords_dict.keys()

        # loop selected keywords and create mRSL string

        resubmit_job_string = ''

        for dict_elem in resubmit_items:
            value = ''
            # Extract job value with fallback to default to support optional
            # fields
            job_value = mrsl_dict.get(dict_elem,
                                      keywords_dict[dict_elem]['Value'])
            if keywords_dict[dict_elem]['Type'].startswith(
                    'multiplekeyvalues'):
                for (elem_key, elem_val) in job_value:
                    if elem_key:
                        value += '%s=%s\n' % (str(elem_key).strip(),
                                              str(elem_val).strip())
            elif keywords_dict[dict_elem]['Type'].startswith('multiple'):
                for elem in job_value:
                    if elem:
                        value += '%s\n' % str(elem).rstrip()
            else:
                if str(job_value):
                    value += '%s\n' % str(job_value).rstrip()

            # Only insert keywords with an associated value

            if value:
                if value.rstrip() != '':
                    resubmit_job_string += '''::%s::
%s

''' % (dict_elem, value.rstrip())

        # save tempfile

        (filehandle, tempfilename) = \
            tempfile.mkstemp(dir=configuration.mig_system_files,
                             text=True)
        os.write(filehandle, resubmit_job_string)
        os.close(filehandle)

        # submit job the usual way

        (new_job_status, msg, new_job_id) = new_job(tempfilename, client_id,
                                                    configuration, False, True)
        if not new_job_status:
            resubmitobj['status'] = False
            resubmitobj['message'] = msg
            status = returnvalues.SYSTEM_ERROR
            resubmitobjs.append(resubmitobj)
            continue

            # o.out("Resubmit failed: %s" % msg)
            # o.reply_and_exit(o.ERROR)

        resubmitobj['status'] = True
        resubmitobj['new_job_id'] = new_job_id
        resubmitobjs.append(resubmitobj)

        # o.out("Resubmit successful: %s" % msg)
        # o.out("%s" % msg)

    output_objects.append({
        'object_type': 'resubmitobjs',
        'resubmitobjs': resubmitobjs
    })

    return (output_objects, status)
示例#30
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,
        # NOTE: path can use wildcards, dst and current_dir cannot
        typecheck_overrides={'path': valid_path_pattern},
    )
    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 as 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

    if dst and not write_file(''.join(all_lines), abs_dest, logger):
        output_objects.append({
            'object_type':
            'error_text',
            'text':
            "failed to write checksums to %s" % relative_dest
        })
        logger.error("writing checksums to %s for %s failed" %
                     (abs_dest, client_id))
        status = returnvalues.SYSTEM_ERROR

    return (output_objects, status)