pkinit_open_session(krb5_context context,
                    pkinit_identity_crypto_context cctx)
{
    CK_ULONG i, r;
    unsigned char *cp;
    size_t label_len;
    CK_ULONG count = 0;
    CK_SLOT_ID_PTR slotlist;
    CK_TOKEN_INFO tinfo;
    char *p11name;
    const char *password;

    if (cctx->p11_module != NULL)
        return 0; /* session already open */

    /* Load module */
    cctx->p11_module =
        pkinit_C_LoadModule(cctx->p11_module_name, &cctx->p11);
    if (cctx->p11_module == NULL)
        return KRB5KDC_ERR_PREAUTH_FAILED;

    /* Init */
    if ((r = cctx->p11->C_Initialize(NULL)) != CKR_OK) {
        pkiDebug("C_Initialize: %s\n", pkinit_pkcs11_code_to_text(r));
        return KRB5KDC_ERR_PREAUTH_FAILED;
    }

    /* Get the list of available slots */
    if (cctx->p11->C_GetSlotList(TRUE, NULL, &count) != CKR_OK)
        return KRB5KDC_ERR_PREAUTH_FAILED;
    if (count == 0)
        return KRB5KDC_ERR_PREAUTH_FAILED;
    slotlist = calloc(count, sizeof(CK_SLOT_ID));
    if (slotlist == NULL)
        return ENOMEM;
    if (cctx->p11->C_GetSlotList(TRUE, slotlist, &count) != CKR_OK)
        return KRB5KDC_ERR_PREAUTH_FAILED;

    /* Look for the given token label, or if none given take the first one */
    for (i = 0; i < count; i++) {
        /* Skip slots that don't match the specified slotid, if given. */
        if (cctx->slotid != PK_NOSLOT && cctx->slotid != slotlist[i])
            continue;

        /* Open session */
        if ((r = cctx->p11->C_OpenSession(slotlist[i], CKF_SERIAL_SESSION,
                                          NULL, NULL, &cctx->session)) != CKR_OK) {
            pkiDebug("C_OpenSession: %s\n", pkinit_pkcs11_code_to_text(r));
            return KRB5KDC_ERR_PREAUTH_FAILED;
        }

        /* Get token info */
        if ((r = cctx->p11->C_GetTokenInfo(slotlist[i], &tinfo)) != CKR_OK) {
            pkiDebug("C_GetTokenInfo: %s\n", pkinit_pkcs11_code_to_text(r));
            return KRB5KDC_ERR_PREAUTH_FAILED;
        }

        /* tinfo.label is zero-filled but not necessarily zero-terminated.
         * Find the length, ignoring any trailing spaces. */
        for (cp = tinfo.label + sizeof(tinfo.label); cp > tinfo.label; cp--) {
            if (cp[-1] != '\0' && cp[-1] != ' ')
                break;
        }
        label_len = cp - tinfo.label;

        pkiDebug("open_session: slotid %d token \"%.*s\"\n",
                 (int)slotlist[i], (int)label_len, tinfo.label);
        if (cctx->token_label == NULL ||
            (strlen(cctx->token_label) == label_len &&
             memcmp(cctx->token_label, tinfo.label, label_len) == 0))
            break;
        cctx->p11->C_CloseSession(cctx->session);
    }
    if (i >= count) {
        free(slotlist);
        pkiDebug("open_session: no matching token found\n");
        return KRB5KDC_ERR_PREAUTH_FAILED;
    }
    cctx->slotid = slotlist[i];
    free(slotlist);
    pkiDebug("open_session: slotid %d (%lu of %d)\n", (int)cctx->slotid,
             i + 1, (int) count);

    /* Login if needed */
    if (tinfo.flags & CKF_LOGIN_REQUIRED) {
        if (cctx->p11_module_name != NULL) {
            if (cctx->slotid != PK_NOSLOT) {
                if (asprintf(&p11name,
                             "PKCS11:module_name=%s:slotid=%ld:token=%.*s",
                             cctx->p11_module_name, (long)cctx->slotid,
                             (int)label_len, tinfo.label) < 0)
                    p11name = NULL;
            } else {
                if (asprintf(&p11name,
                             "PKCS11:module_name=%s,token=%.*s",
                             cctx->p11_module_name,
                             (int)label_len, tinfo.label) < 0)
                    p11name = NULL;
            }
        } else {
            p11name = NULL;
        }
        if (cctx->defer_id_prompt) {
            /* Supply the identity name to be passed to the responder. */
            pkinit_set_deferred_id(&cctx->deferred_ids,
                                   p11name, tinfo.flags, NULL);
            free(p11name);
            return KRB5KRB_ERR_GENERIC;
        }
        /* Look up a responder-supplied password for the token. */
        password = pkinit_find_deferred_id(cctx->deferred_ids, p11name);
        free(p11name);
        r = pkinit_login(context, cctx, &tinfo, password);
    }

    return r;
}