예제 #1
0
/*
 * Function: client_establish_context
 *
 * Purpose: establishes a GSS-API context with a specified service and
 * returns the context handle
 *
 * Arguments:
 *
 *      s                   (r) an established TCP connection to the service
 *      service_name(r) the ASCII service name of the service
 *      gss_flags       (r) GSS-API delegation flag (if any)
 *      auth_flag       (r) whether to actually do authentication
 *  v1_format   (r) whether the v1 sample protocol should be used
 *      oid                 (r) OID of the mechanism to use
 *      context         (w) the established GSS-API context
 *      ret_flags       (w) the returned flags from init_sec_context
 *
 * Returns: 0 on success, -1 on failure
 *
 * Effects:
 *
 * service_name is imported as a GSS-API name and a GSS-API context is
 * established with the corresponding service; the service should be
 * listening on the TCP connection s.  The default GSS-API mechanism
 * is used, and mutual authentication and replay detection are
 * requested.
 *
 * If successful, the context handle is returned in context.  If
 * unsuccessful, the GSS-API error messages are displayed on stderr
 * and -1 is returned.
 */
static int
client_establish_context(int s, char *service_name, OM_uint32 gss_flags,
                         int auth_flag, int v1_format, gss_OID oid,
                         char *username, char *password,
                         gss_ctx_id_t *gss_context, OM_uint32 *ret_flags)
{
    if (auth_flag) {
        gss_buffer_desc send_tok, recv_tok, *token_ptr;
        gss_name_t target_name;
        OM_uint32 maj_stat, min_stat, init_sec_min_stat;
        int token_flags;
        gss_cred_id_t cred = GSS_C_NO_CREDENTIAL;
        gss_name_t gss_username = GSS_C_NO_NAME;
        gss_OID_set_desc mechs, *mechsp = GSS_C_NO_OID_SET;
#ifndef MECH_EAP
        struct gss_channel_bindings_struct cb = {GSS_C_AF_NULLADDR, {0, NULL},
                                                 GSS_C_AF_NULLADDR, {0, NULL},
            {strlen("HLJHLJHLJHLJHJKLHLJHLJH"), "HLJHLJHLJHLJHJKLHLJHLJH"}};
#endif

        if (spnego) {
            mechs.elements = &gss_spnego_mechanism_oid_desc;
            mechs.count = 1;
            mechsp = &mechs;
        } else if (oid != GSS_C_NO_OID) {
            mechs.elements = oid;
            mechs.count = 1;
            mechsp = &mechs;
        } else {
            mechs.elements = NULL;
            mechs.count = 0;
        }

        if (username != NULL) {
            send_tok.value = username;
            send_tok.length = strlen(username);

            maj_stat = gss_import_name(&min_stat, &send_tok,
                                       (gss_OID) gss_nt_user_name,
                                       &gss_username);
            if (maj_stat != GSS_S_COMPLETE) {
                display_status("parsing client name", maj_stat, min_stat);
                return -1;
            }
        }

        if (password != NULL) {
            gss_buffer_desc pwbuf;

            pwbuf.value = password;
            pwbuf.length = strlen(password);

            maj_stat = gss_acquire_cred_with_password(&min_stat,
                                                      gss_username,
                                                      &pwbuf, 0,
                                                      mechsp, GSS_C_INITIATE,
                                                      &cred, NULL, NULL);
        } else if (gss_username != GSS_C_NO_NAME) {
            maj_stat = gss_acquire_cred(&min_stat,
                                        gss_username, 0,
                                        mechsp, GSS_C_INITIATE,
                                        &cred, NULL, NULL);
        } else
            maj_stat = GSS_S_COMPLETE;
        if (maj_stat != GSS_S_COMPLETE) {
            display_status("acquiring creds", maj_stat, min_stat);
            gss_release_name(&min_stat, &gss_username);
            return -1;
        }
        if (spnego && oid != GSS_C_NO_OID) {
            gss_OID_set_desc neg_mechs;

            neg_mechs.elements = oid;
            neg_mechs.count = 1;

            maj_stat = gss_set_neg_mechs(&min_stat, cred, &neg_mechs);
            if (maj_stat != GSS_S_COMPLETE) {
                display_status("setting neg mechs", maj_stat, min_stat);
                gss_release_name(&min_stat, &gss_username);
                gss_release_cred(&min_stat, &cred);
                return -1;
            }
        }
        gss_release_name(&min_stat, &gss_username);

        /*
         * Import the name into target_name.  Use send_tok to save
         * local variable space.
         */
        send_tok.value = service_name;
        send_tok.length = strlen(service_name);
        maj_stat = gss_import_name(&min_stat, &send_tok,
                                   (gss_OID) gss_nt_service_name,
                                   &target_name);
        if (maj_stat != GSS_S_COMPLETE) {
            display_status("parsing name", maj_stat, min_stat);
            return -1;
        }

        if (!v1_format) {
            if (send_token(s, TOKEN_NOOP | TOKEN_CONTEXT_NEXT, empty_token) <
                0) {
                (void) gss_release_name(&min_stat, &target_name);
                return -1;
            }
        }

        /*
         * Perform the context-establishement loop.
         *
         * On each pass through the loop, token_ptr points to the token
         * to send to the server (or GSS_C_NO_BUFFER on the first pass).
         * Every generated token is stored in send_tok which is then
         * transmitted to the server; every received token is stored in
         * recv_tok, which token_ptr is then set to, to be processed by
         * the next call to gss_init_sec_context.
         *
         * GSS-API guarantees that send_tok's length will be non-zero
         * if and only if the server is expecting another token from us,
         * and that gss_init_sec_context returns GSS_S_CONTINUE_NEEDED if
         * and only if the server has another token to send us.
         */

        token_ptr = GSS_C_NO_BUFFER;
        *gss_context = GSS_C_NO_CONTEXT;

        do {
            maj_stat = gss_init_sec_context(&init_sec_min_stat,
                                            cred, gss_context,
                                            target_name, mechs.elements,
                                            gss_flags, 0,
#ifdef MECH_EAP
                                            NULL, /* channel bindings */
#else
                                            &cb,
#endif
                                            token_ptr, NULL, /* mech type */
                                            &send_tok, ret_flags,
                                            NULL);  /* time_rec */

            if (token_ptr != GSS_C_NO_BUFFER)
                free(recv_tok.value);

            if (send_tok.length != 0) {
                if (verbose)
                    printf("Sending init_sec_context token (size=%d)...",
                           (int) send_tok.length);
                if (send_token(s, v1_format ? 0 : TOKEN_CONTEXT, &send_tok) <
                    0) {
                    (void) gss_release_buffer(&min_stat, &send_tok);
                    (void) gss_release_name(&min_stat, &target_name);
                    return -1;
                }
            }
            (void) gss_release_buffer(&min_stat, &send_tok);

            if (maj_stat != GSS_S_COMPLETE
                && maj_stat != GSS_S_CONTINUE_NEEDED) {
                display_status("initializing context", maj_stat,
                               init_sec_min_stat);
                (void) gss_release_name(&min_stat, &target_name);
                if (*gss_context != GSS_C_NO_CONTEXT)
                    gss_delete_sec_context(&min_stat, gss_context,
                                           GSS_C_NO_BUFFER);
                return -1;
            }

            if (maj_stat == GSS_S_CONTINUE_NEEDED) {
                if (verbose)
                    printf("continue needed...");
                if (recv_token(s, &token_flags, &recv_tok) < 0) {
                    (void) gss_release_name(&min_stat, &target_name);
                    return -1;
                }
                token_ptr = &recv_tok;
            }
            if (verbose)
                printf("\n");
        } while (maj_stat == GSS_S_CONTINUE_NEEDED);

        (void) gss_release_cred(&min_stat, &cred);
        (void) gss_release_name(&min_stat, &target_name);
    } else {
        if (send_token(s, TOKEN_NOOP, empty_token) < 0)
            return -1;
    }

    return 0;
}
예제 #2
0
static int mag_auth(request_rec *req)
{
    const char *type;
    int auth_type = -1;
    struct mag_req_cfg *req_cfg;
    struct mag_config *cfg;
    const char *auth_header;
    char *auth_header_type;
    int ret = HTTP_UNAUTHORIZED;
    gss_ctx_id_t ctx = GSS_C_NO_CONTEXT;
    gss_ctx_id_t *pctx;
    gss_buffer_desc input = GSS_C_EMPTY_BUFFER;
    gss_buffer_desc output = GSS_C_EMPTY_BUFFER;
    gss_buffer_desc name = GSS_C_EMPTY_BUFFER;
    gss_buffer_desc ba_user;
    gss_buffer_desc ba_pwd;
    gss_name_t client = GSS_C_NO_NAME;
    gss_cred_id_t acquired_cred = GSS_C_NO_CREDENTIAL;
    gss_cred_id_t delegated_cred = GSS_C_NO_CREDENTIAL;
    gss_cred_usage_t cred_usage = GSS_C_ACCEPT;
    uint32_t vtime;
    uint32_t maj, min;
    char *reply;
    size_t replen;
    gss_OID mech_type = GSS_C_NO_OID;
    gss_OID_set desired_mechs = GSS_C_NO_OID_SET;
    gss_buffer_desc lname = GSS_C_EMPTY_BUFFER;
    struct mag_conn *mc = NULL;
    int i;
    bool send_auth_header = true;

    type = ap_auth_type(req);
    if ((type == NULL) || (strcasecmp(type, "GSSAPI") != 0)) {
        return DECLINED;
    }

    req_cfg = mag_init_cfg(req);

    cfg = req_cfg->cfg;

    desired_mechs = req_cfg->desired_mechs;

    /* implicit auth for subrequests if main auth already happened */
    if (!ap_is_initial_req(req) && req->main != NULL) {
        type = ap_auth_type(req->main);
        if ((type != NULL) && (strcasecmp(type, "GSSAPI") == 0)) {
            /* warn if the subrequest location and the main request
             * location have different configs */
            if (cfg != ap_get_module_config(req->main->per_dir_config,
                                            &auth_gssapi_module)) {
                ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0,
                              req, "Subrequest authentication bypass on "
                                   "location with different configuration!");
            }
            if (req->main->user) {
                req->user = apr_pstrdup(req->pool, req->main->user);
                return OK;
            } else {
                ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
                              "The main request is tasked to establish the "
                              "security context, can't proceed!");
                return HTTP_UNAUTHORIZED;
            }
        } else {
            ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, req,
                          "Subrequest GSSAPI auth with no auth on the main "
                          "request. This operation may fail if other "
                          "subrequests already established a context or the "
                          "mechanism requires multiple roundtrips.");
        }
    }

    if (cfg->ssl_only) {
        if (!mag_conn_is_https(req->connection)) {
            ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
                          "Not a TLS connection, refusing to authenticate!");
            goto done;
        }
    }

    if (cfg->gss_conn_ctx) {
        mc = (struct mag_conn *)ap_get_module_config(
                                                req->connection->conn_config,
                                                &auth_gssapi_module);
        if (!mc) {
            ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, req,
                          "Failed to retrieve connection context!");
            goto done;
        }
    }

    /* if available, session always supersedes connection bound data */
    if (req_cfg->use_sessions) {
        mag_check_session(req_cfg, &mc);
    }

    auth_header = apr_table_get(req->headers_in, req_cfg->req_proto);

    if (mc) {
        if (mc->established &&
            (auth_header == NULL) &&
            (mc->auth_type != AUTH_TYPE_BASIC)) {
            ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, req,
                          "Already established context found!");
            mag_set_req_data(req, cfg, mc);
            ret = OK;
            goto done;
        }
        pctx = &mc->ctx;
    } else {
        /* no preserved mc, create one just for this request */
        mc = mag_new_conn_ctx(req->pool);
        pctx = &ctx;
    }

    /* We can proceed only if we do have an auth header */
    if (!auth_header) goto done;

    auth_header_type = ap_getword_white(req->pool, &auth_header);
    if (!auth_header_type) goto done;

    /* We got auth header, sending auth header would mean re-auth */
    send_auth_header = !cfg->negotiate_once;

    for (i = 0; auth_types[i] != NULL; i++) {
        if (strcasecmp(auth_header_type, auth_types[i]) == 0) {
            auth_type = i;
            break;
        }
    }

    switch (auth_type) {
    case AUTH_TYPE_NEGOTIATE:
        if (!parse_auth_header(req->pool, &auth_header, &input)) {
            goto done;
        }
        break;
    case AUTH_TYPE_BASIC:
        if (!cfg->use_basic_auth) {
            goto done;
        }

        ba_pwd.value = ap_pbase64decode(req->pool, auth_header);
        if (!ba_pwd.value) goto done;
        ba_user.value = ap_getword_nulls_nc(req->pool,
                                            (char **)&ba_pwd.value, ':');
        if (!ba_user.value) goto done;

        if (((char *)ba_user.value)[0] == '\0' ||
            ((char *)ba_pwd.value)[0] == '\0') {
            ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
                          "Invalid empty user or password for Basic Auth");
            goto done;
        }
        ba_user.length = strlen(ba_user.value);
        ba_pwd.length = strlen(ba_pwd.value);

        if (mc->is_preserved && mc->established &&
            mag_basic_check(req_cfg, mc, ba_user, ba_pwd)) {
            ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, req,
                          "Already established BASIC AUTH context found!");
            mag_set_req_data(req, cfg, mc);
            ret = OK;
            goto done;
        }

        break;

    case AUTH_TYPE_RAW_NTLM:
        if (!is_mech_allowed(desired_mechs, gss_mech_ntlmssp,
                             cfg->gss_conn_ctx)) {
            ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, req,
                          "NTLM Authentication is not allowed!");
            goto done;
        }

        if (!parse_auth_header(req->pool, &auth_header, &input)) {
            goto done;
        }

        desired_mechs = discard_const(gss_mech_set_ntlmssp);
        break;

    default:
        goto done;
    }

    if (mc->established) {
        /* if we are re-authenticating make sure the conn context
         * is cleaned up so we do not accidentally reuse an existing
         * established context */
        mag_conn_clear(mc);
    }

    mc->auth_type = auth_type;

#ifdef HAVE_CRED_STORE
    if (use_s4u2proxy(req_cfg)) {
        cred_usage = GSS_C_BOTH;
    }
#endif

    if (auth_type == AUTH_TYPE_BASIC) {
        if (mag_auth_basic(req, cfg, ba_user, ba_pwd,
                           &client, &mech_type,
                           &delegated_cred, &vtime)) {
            goto complete;
        }
        goto done;
    }

    if (!mag_acquire_creds(req, cfg, desired_mechs,
                           cred_usage, &acquired_cred, NULL)) {
        goto done;
    }

    if (auth_type == AUTH_TYPE_NEGOTIATE &&
        cfg->allowed_mechs != GSS_C_NO_OID_SET) {
        maj = gss_set_neg_mechs(&min, acquired_cred, cfg->allowed_mechs);
        if (GSS_ERROR(maj)) {
            ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req, "%s",
                          mag_error(req, "gss_set_neg_mechs() failed",
                                    maj, min));
            goto done;
        }
    }

    maj = gss_accept_sec_context(&min, pctx, acquired_cred,
                                 &input, GSS_C_NO_CHANNEL_BINDINGS,
                                 &client, &mech_type, &output, NULL, &vtime,
                                 &delegated_cred);
    if (GSS_ERROR(maj)) {
        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req, "%s",
                      mag_error(req, "gss_accept_sec_context() failed",
                                maj, min));
        goto done;
    } else if (maj == GSS_S_CONTINUE_NEEDED) {
        if (!mc->is_preserved) {
            ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
                          "Mechanism needs continuation but neither "
                          "GssapiConnectionBound nor "
                          "GssapiUseSessions are available");
            gss_release_buffer(&min, &output);
            output.length = 0;
        }
        /* auth not complete send token and wait next packet */
        goto done;
    }

complete:
    maj = gss_display_name(&min, client, &name, NULL);
    if (GSS_ERROR(maj)) {
        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req, "%s",
                      mag_error(req, "gss_display_name() failed",
                                maj, min));
        goto done;
    }

    mc->gss_name = apr_pstrndup(req->pool, name.value, name.length);
    if (vtime == GSS_C_INDEFINITE || vtime < MIN_SESS_EXP_TIME) {
        vtime = MIN_SESS_EXP_TIME;
    }
    mc->expiration = time(NULL) + vtime;

    mag_get_name_attributes(req, cfg, client, mc);

#ifdef HAVE_CRED_STORE
    if (cfg->deleg_ccache_dir && delegated_cred != GSS_C_NO_CREDENTIAL) {
        char *ccache_path;

        mc->ccname = 0;
        ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, req,
                      "requester: %s", mc->gss_name);

        ccache_path = get_ccache_name(req, cfg->deleg_ccache_dir, mc->gss_name,
                                      cfg->deleg_ccache_unique, mc);
        if (ccache_path == NULL) {
            goto done;
        }

        mag_store_deleg_creds(req, ccache_path, delegated_cred);
        mc->delegated = true;

        if (!req_cfg->use_sessions && cfg->deleg_ccache_unique) {
            /* queue removing ccache to avoid littering filesystem */
            apr_pool_cleanup_register(mc->pool, ccache_path,
                                      (int (*)(void *)) unlink,
                                      apr_pool_cleanup_null);
        }

        /* extract filename from full path */
        mc->ccname = strrchr(ccache_path, '/') + 1;
    }
#endif

    if (cfg->map_to_local) {
        maj = gss_localname(&min, client, mech_type, &lname);
        if (maj != GSS_S_COMPLETE) {
            ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req, "%s",
                          mag_error(req, "gss_localname() failed", maj, min));
            goto done;
        }
        mc->user_name = apr_pstrndup(req->pool, lname.value, lname.length);
    } else {
        mc->user_name = apr_pstrdup(mc->pool, mc->gss_name);
    }

    mc->established = true;
    if (auth_type == AUTH_TYPE_BASIC) {
        mag_basic_cache(req_cfg, mc, ba_user, ba_pwd);
    }
    if (req_cfg->use_sessions) {
        mag_attempt_session(req_cfg, mc);
    }

    /* Now set request data and env vars */
    mag_set_req_data(req, cfg, mc);

    if (req_cfg->send_persist)
        apr_table_set(req->headers_out, "Persistent-Auth",
            cfg->gss_conn_ctx ? "true" : "false");

    ret = OK;

done:

    if ((auth_type != AUTH_TYPE_BASIC) && (output.length != 0)) {
        int prefixlen = strlen(mag_str_auth_type(auth_type)) + 1;
        replen = apr_base64_encode_len(output.length) + 1;
        reply = apr_pcalloc(req->pool, prefixlen + replen);
        if (reply) {
            memcpy(reply, mag_str_auth_type(auth_type), prefixlen - 1);
            reply[prefixlen - 1] = ' ';
            apr_base64_encode(&reply[prefixlen], output.value, output.length);
            apr_table_add(req->err_headers_out, req_cfg->rep_proto, reply);
        }
    } else if (ret == HTTP_UNAUTHORIZED) {
        if (send_auth_header) {
            apr_table_add(req->err_headers_out,
                          req_cfg->rep_proto, "Negotiate");
            if (is_mech_allowed(desired_mechs, gss_mech_ntlmssp,
                                cfg->gss_conn_ctx)) {
                apr_table_add(req->err_headers_out, req_cfg->rep_proto,
                              "NTLM");
            }
        }
        if (cfg->use_basic_auth) {
            apr_table_add(req->err_headers_out, req_cfg->rep_proto,
                          apr_psprintf(req->pool, "Basic realm=\"%s\"",
                                       ap_auth_name(req)));
        }
    }

    if (ctx != GSS_C_NO_CONTEXT)
        gss_delete_sec_context(&min, &ctx, GSS_C_NO_BUFFER);
    gss_release_cred(&min, &acquired_cred);
    gss_release_cred(&min, &delegated_cred);
    gss_release_buffer(&min, &output);
    gss_release_name(&min, &client);
    gss_release_buffer(&min, &name);
    gss_release_buffer(&min, &lname);
    return ret;
}