#include <k5-platform.h>
#include <k5-json.h>
#include <sys/types.h>
#include <unistd.h>
#include <krb5.h>
struct responder_data {
krb5_boolean called;
krb5_boolean print_pkinit_challenge;
const char *challenge;
const char *response;
const char *pkinit_answer;
const char *otp_answer;
};
static krb5_error_code
responder(krb5_context ctx, void *rawdata, krb5_responder_context rctx)
{
krb5_error_code err;
char *key, *value, *pin, *encoded1, *encoded2;
const char *challenge;
k5_json_value decoded1, decoded2;
k5_json_object ids;
k5_json_number val;
krb5_int32 token_flags;
struct responder_data *data = rawdata;
krb5_responder_pkinit_challenge *chl;
krb5_responder_otp_challenge *ochl;
unsigned int i, n;
data->called = TRUE;
if (data->challenge != NULL) {
key = strdup(data->challenge);
if (key == NULL)
exit(ENOMEM);
value = key + strcspn(key, "=");
if (*value != '\0')
*value++ = '\0';
challenge = krb5_responder_get_challenge(ctx, rctx, key);
err = k5_json_decode(value, &decoded1);
if (challenge == NULL && *value == '\0') {
fprintf(stderr, "OK: (no challenge) == (no challenge)\n");
} else if (err != 0) {
if (strcmp(challenge, value) == 0) {
fprintf(stderr, "OK: \"%s\" == \"%s\"\n", challenge, value);
} else {
fprintf(stderr, "ERROR: \"%s\" != \"%s\"\n", challenge, value);
exit(1);
}
} else {
err = k5_json_decode(challenge, &decoded2);
if (err != 0) {
fprintf(stderr, "error decoding \"%s\"\n", challenge);
exit(1);
}
err = k5_json_encode(decoded1, &encoded1);
if (err != 0) {
fprintf(stderr, "error encoding json data\n");
exit(1);
}
err = k5_json_encode(decoded2, &encoded2);
if (err != 0) {
fprintf(stderr, "error encoding json data\n");
exit(1);
}
k5_json_release(decoded1);
k5_json_release(decoded2);
if (strcmp(encoded1, encoded2) == 0) {
fprintf(stderr, "OK: \"%s\" == \"%s\"\n", encoded1, encoded2);
} else {
fprintf(stderr, "ERROR: \"%s\" != \"%s\"\n", encoded1,
encoded2);
exit(1);
}
free(encoded1);
free(encoded2);
}
free(key);
}
if (data->response != NULL) {
key = strdup(data->response);
if (key == NULL)
exit(ENOMEM);
value = key + strcspn(key, "=");
if (*value != '\0')
*value++ = '\0';
err = krb5_responder_set_answer(ctx, rctx, key, value);
if (err != 0) {
fprintf(stderr, "error setting response\n");
exit(1);
}
free(key);
}
if (data->print_pkinit_challenge) {
err = krb5_responder_pkinit_get_challenge(ctx, rctx, &chl);
if (err != 0) {
fprintf(stderr, "error getting pkinit challenge\n");
exit(1);
}
if (chl != NULL) {
for (n = 0; chl->identities[n] != NULL; n++)
continue;
for (i = 0; chl->identities[i] != NULL; i++) {
if (chl->identities[i]->token_flags != -1) {
printf("identity %u/%u: %s (flags=0x%lx)\n", i + 1, n,
chl->identities[i]->identity,
(long)chl->identities[i]->token_flags);
} else {
printf("identity %u/%u: %s\n", i + 1, n,
chl->identities[i]->identity);
}
}
}
krb5_responder_pkinit_challenge_free(ctx, rctx, chl);
}
if (data->pkinit_answer != NULL) {
err = krb5_responder_pkinit_get_challenge(ctx, rctx, &chl);
if (err != 0) {
fprintf(stderr, "error getting pkinit challenge\n");
exit(1);
}
if (chl != NULL &&
chl->identities != NULL &&
chl->identities[0] != NULL) {
if (strncmp(chl->identities[0]->identity, "FILE:", 5) == 0)
krb5_responder_pkinit_set_answer(ctx, rctx, "foo", "bar");
}
key = strdup(data->pkinit_answer);
if (key == NULL)
exit(ENOMEM);
value = strrchr(key, '=');
if (value != NULL)
*value++ = '\0';
else
value = "";
err = krb5_responder_pkinit_set_answer(ctx, rctx, key, value);
if (err != 0) {
fprintf(stderr, "error setting response\n");
exit(1);
}
free(key);
if (chl != NULL &&
chl->identities != NULL &&
chl->identities[0] != NULL) {
if (strncmp(chl->identities[0]->identity, "PKCS12:", 7) == 0)
krb5_responder_pkinit_set_answer(ctx, rctx, "foo", "bar");
}
krb5_responder_pkinit_challenge_free(ctx, rctx, chl);
}
challenge = krb5_responder_get_challenge(ctx, rctx,
KRB5_RESPONDER_QUESTION_PKINIT);
if (challenge != NULL) {
krb5_responder_pkinit_get_challenge(ctx, rctx, &chl);
if (chl == NULL) {
fprintf(stderr, "pkinit raw challenge set, "
"but structure is NULL\n");
exit(1);
}
if (k5_json_object_create(&ids) != 0) {
fprintf(stderr, "error creating json objects\n");
exit(1);
}
for (i = 0; chl->identities[i] != NULL; i++) {
token_flags = chl->identities[i]->token_flags;
if (k5_json_number_create(token_flags, &val) != 0) {
fprintf(stderr, "error creating json number\n");
exit(1);
}
if (k5_json_object_set(ids, chl->identities[i]->identity,
val) != 0) {
fprintf(stderr, "error adding json number to object\n");
exit(1);
}
k5_json_release(val);
}
err = k5_json_encode(ids, &encoded1);
if (err != 0) {
fprintf(stderr, "error encoding json data\n");
exit(1);
}
k5_json_release(ids);
if (strcmp(encoded1, challenge) != 0) {
fprintf(stderr, "\"%s\" != \"%s\"\n", encoded1, challenge);
exit(1);
}
krb5_responder_pkinit_challenge_free(ctx, rctx, chl);
free(encoded1);
}
if (data->otp_answer != NULL) {
if (krb5_responder_otp_get_challenge(ctx, rctx, &ochl) == 0) {
key = strchr(data->otp_answer, '=');
if (key != NULL) {
key = strdup(data->otp_answer);
if (key == NULL)
return ENOMEM;
value = strchr(key, '=');
*value++ = '\0';
n = atoi(key);
pin = strchr(value, ':');
if (pin != NULL)
*pin++ = '\0';
err = krb5_responder_otp_set_answer(ctx, rctx, n, value, pin);
if (err != 0) {
fprintf(stderr, "error setting response\n");
exit(1);
}
free(key);
}
krb5_responder_otp_challenge_free(ctx, rctx, ochl);
}
}
return 0;
}
int
main(int argc, char **argv)
{
krb5_context context;
krb5_ccache ccache;
krb5_get_init_creds_opt *opts;
krb5_principal principal;
krb5_creds creds;
krb5_error_code err;
const char *errmsg;
char *opt, *val;
struct responder_data response;
int c;
err = krb5_init_context(&context);
if (err != 0) {
fprintf(stderr, "error starting Kerberos: %s\n", error_message(err));
return err;
}
err = krb5_get_init_creds_opt_alloc(context, &opts);
if (err != 0) {
fprintf(stderr, "error initializing options: %s\n",
error_message(err));
return err;
}
err = krb5_cc_default(context, &ccache);
if (err != 0) {
fprintf(stderr, "error resolving default ccache: %s\n",
error_message(err));
return err;
}
err = krb5_get_init_creds_opt_set_out_ccache(context, opts, ccache);
if (err != 0) {
fprintf(stderr, "error setting output ccache: %s\n",
error_message(err));
return err;
}
memset(&response, 0, sizeof(response));
while ((c = getopt(argc, argv, "X:x:cr:p:")) != -1) {
switch (c) {
case 'X':
opt = strdup(optarg);
val = opt + strcspn(opt, "=");
if (*val != '\0') {
*val++ = '\0';
}
err = krb5_get_init_creds_opt_set_pa(context, opts, opt, val);
if (err != 0) {
fprintf(stderr, "error setting option \"%s\": %s\n", opt,
error_message(err));
return err;
}
free(opt);
break;
case 'x':
response.challenge = optarg;
break;
case 'c':
response.print_pkinit_challenge = TRUE;
break;
case 'r':
response.response = optarg;
break;
case 'p':
response.pkinit_answer = optarg;
break;
case 'o':
response.otp_answer = optarg;
break;
}
}
if (argc > optind) {
err = krb5_parse_name(context, argv[optind], &principal);
if (err != 0) {
fprintf(stderr, "error parsing name \"%s\": %s", argv[optind],
error_message(err));
return err;
}
} else {
fprintf(stderr, "error: no principal name provided\n");
return -1;
}
err = krb5_get_init_creds_opt_set_responder(context, opts,
responder, &response);
if (err != 0) {
fprintf(stderr, "error setting responder: %s\n", error_message(err));
return err;
}
memset(&creds, 0, sizeof(creds));
err = krb5_get_init_creds_password(context, &creds, principal, NULL,
NULL, NULL, 0, NULL, opts);
if (err == 0)
krb5_free_cred_contents(context, &creds);
krb5_free_principal(context, principal);
krb5_get_init_creds_opt_free(context, opts);
krb5_cc_close(context, ccache);
if (!response.called) {
fprintf(stderr, "error: responder callback wasn't called\n");
err = 1;
} else if (err) {
errmsg = krb5_get_error_message(context, err);
fprintf(stderr, "error: krb5_get_init_creds_password failed: %s\n",
errmsg);
krb5_free_error_message(context, errmsg);
err = 2;
}
krb5_free_context(context);
return err;
}