/* * Copyright (c) 2006 Kungliga Tekniska Högskolan * (Royal Institute of Technology, Stockholm, Sweden). * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * 3. Neither the name of KTH nor the names of its contributors may be * used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY KTH AND ITS CONTRIBUTORS ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL KTH OR ITS CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include RCSID("$Id$"); static FILE *logfile; /* * */ struct client { char *name; struct sockaddr *sa; socklen_t salen; krb5_storage *sock; int32_t capabilities; char *target_name; char *moniker; krb5_storage *logsock; int have_log; #ifdef ENABLE_PTHREAD_SUPPORT pthread_t thr; #else pid_t child; #endif }; static struct client **clients; static int num_clients; static int init_sec_context(struct client *client, int32_t *hContext, int32_t *hCred, int32_t flags, const char *targetname, const krb5_data *itoken, krb5_data *otoken) { int32_t val; krb5_data_zero(otoken); put32(client, eInitContext); put32(client, *hContext); put32(client, *hCred); put32(client, flags); putstring(client, targetname); putdata(client, *itoken); ret32(client, *hContext); ret32(client, val); retdata(client, *otoken); return val; } static int accept_sec_context(struct client *client, int32_t *hContext, int32_t flags, const krb5_data *itoken, krb5_data *otoken, int32_t *hDelegCred) { int32_t val; krb5_data_zero(otoken); put32(client, eAcceptContext); put32(client, *hContext); put32(client, flags); putdata(client, *itoken); ret32(client, *hContext); ret32(client, val); retdata(client, *otoken); ret32(client, *hDelegCred); return val; } static int acquire_cred(struct client *client, const char *username, const char *password, int32_t flags, int32_t *hCred) { int32_t val; put32(client, eAcquireCreds); putstring(client, username); putstring(client, password); put32(client, flags); ret32(client, val); ret32(client, *hCred); return val; } static int toast_resource(struct client *client, int32_t hCred) { int32_t val; put32(client, eToastResource); put32(client, hCred); ret32(client, val); return val; } static int goodbye(struct client *client) { put32(client, eGoodBye); return GSMERR_OK; } static int get_targetname(struct client *client, char **target) { put32(client, eGetTargetName); retstring(client, *target); return GSMERR_OK; } static int32_t encrypt_token(struct client *client, int32_t hContext, int32_t flags, krb5_data *in, krb5_data *out) { int32_t val; put32(client, eEncrypt); put32(client, hContext); put32(client, flags); put32(client, 0); putdata(client, *in); ret32(client, val); retdata(client, *out); return val; } static int32_t decrypt_token(struct client *client, int32_t hContext, int flags, krb5_data *in, krb5_data *out) { int32_t val; put32(client, eDecrypt); put32(client, hContext); put32(client, flags); put32(client, 0); putdata(client, *in); ret32(client, val); retdata(client, *out); return val; } static int32_t wrap_token_ext(struct client *client, int32_t hContext, int32_t flags, int32_t bflags, krb5_data *header, krb5_data *in, krb5_data *trailer, krb5_data *out) { int32_t val; put32(client, eWrapExt); put32(client, hContext); put32(client, flags); put32(client, bflags); putdata(client, *header); putdata(client, *in); putdata(client, *trailer); ret32(client, val); retdata(client, *out); return val; } static int32_t unwrap_token_ext(struct client *client, int32_t hContext, int32_t flags, int32_t bflags, krb5_data *header, krb5_data *in, krb5_data *trailer, krb5_data *out) { int32_t val; put32(client, eUnwrapExt); put32(client, hContext); put32(client, flags); put32(client, bflags); putdata(client, *header); putdata(client, *in); putdata(client, *trailer); ret32(client, val); retdata(client, *out); return val; } static int32_t get_mic(struct client *client, int32_t hContext, krb5_data *in, krb5_data *mic) { int32_t val; put32(client, eSign); put32(client, hContext); put32(client, 0); put32(client, 0); putdata(client, *in); ret32(client, val); retdata(client, *mic); return val; } static int32_t verify_mic(struct client *client, int32_t hContext, krb5_data *in, krb5_data *mic) { int32_t val; put32(client, eVerify); put32(client, hContext); put32(client, 0); put32(client, 0); putdata(client, *in); putdata(client, *mic); ret32(client, val); return val; } static int32_t get_version_capa(struct client *client, int32_t *version, int32_t *capa, char **version_str) { put32(client, eGetVersionAndCapabilities); ret32(client, *version); ret32(client, *capa); retstring(client, *version_str); return GSMERR_OK; } static int32_t get_moniker(struct client *client, char **moniker) { put32(client, eGetMoniker); retstring(client, *moniker); return GSMERR_OK; } static int wait_log(struct client *c) { int32_t port; struct sockaddr_storage sast; socklen_t salen = sizeof(sast); int fd, fd2, ret; memset(&sast, 0, sizeof(sast)); assert(sizeof(sast) >= c->salen); fd = socket(c->sa->sa_family, SOCK_STREAM, 0); if (fd < 0) err(1, "failed to build socket for %s's logging port", c->moniker); ((struct sockaddr *)&sast)->sa_family = c->sa->sa_family; ret = bind(fd, (struct sockaddr *)&sast, c->salen); if (ret < 0) err(1, "failed to bind %s's logging port", c->moniker); if (listen(fd, SOMAXCONN) < 0) err(1, "failed to listen %s's logging port", c->moniker); salen = sizeof(sast); ret = getsockname(fd, (struct sockaddr *)&sast, &salen); if (ret < 0) err(1, "failed to get address of local socket for %s", c->moniker); port = socket_get_port((struct sockaddr *)&sast); put32(c, eSetLoggingSocket); put32(c, ntohs(port)); salen = sizeof(sast); fd2 = accept(fd, (struct sockaddr *)&sast, &salen); if (fd2 < 0) err(1, "failed to accept local socket for %s", c->moniker); close(fd); return fd2; } static int build_context(struct client *ipeer, struct client *apeer, int32_t flags, int32_t hCred, int32_t *iContext, int32_t *aContext, int32_t *hDelegCred) { int32_t val = GSMERR_ERROR, ic = 0, ac = 0, deleg = 0; krb5_data itoken, otoken; int iDone = 0, aDone = 0; int step = 0; int first_call = 0x80; if (apeer->target_name == NULL) errx(1, "apeer %s have no target name", apeer->name); krb5_data_zero(&itoken); while (!iDone || !aDone) { if (iDone) { warnx("iPeer already done, aPeer want extra rtt"); val = GSMERR_ERROR; goto out; } val = init_sec_context(ipeer, &ic, &hCred, flags|first_call, apeer->target_name, &itoken, &otoken); step++; switch(val) { case GSMERR_OK: iDone = 1; if (aDone) continue; break; case GSMERR_CONTINUE_NEEDED: break; default: warnx("iPeer %s failed with %d (step %d)", ipeer->name, (int)val, step); goto out; } if (aDone) { warnx("aPeer already done, iPeer want extra rtt"); val = GSMERR_ERROR; goto out; } val = accept_sec_context(apeer, &ac, flags|first_call, &otoken, &itoken, &deleg); step++; switch(val) { case GSMERR_OK: aDone = 1; if (iDone) continue; break; case GSMERR_CONTINUE_NEEDED: break; default: warnx("aPeer %s failed with %d (step %d)", apeer->name, (int)val, step); val = GSMERR_ERROR; goto out; } first_call = 0; val = GSMERR_OK; } if (iContext == NULL || val != GSMERR_OK) { if (ic) toast_resource(ipeer, ic); if (iContext) *iContext = 0; } else *iContext = ic; if (aContext == NULL || val != GSMERR_OK) { if (ac) toast_resource(apeer, ac); if (aContext) *aContext = 0; } else *aContext = ac; if (hDelegCred == NULL || val != GSMERR_OK) { if (deleg) toast_resource(apeer, deleg); if (hDelegCred) *hDelegCred = 0; } else *hDelegCred = deleg; out: return val; } static void test_mic(struct client *c1, int32_t hc1, struct client *c2, int32_t hc2) { krb5_data msg, mic; int32_t val; msg.data = "foo"; msg.length = 3; krb5_data_zero(&mic); val = get_mic(c1, hc1, &msg, &mic); if (val) errx(1, "get_mic failed to host: %s", c1->moniker); val = verify_mic(c2, hc2, &msg, &mic); if (val) errx(1, "verify_mic failed to host: %s", c2->moniker); krb5_data_free(&mic); } static int32_t test_wrap(struct client *c1, int32_t hc1, struct client *c2, int32_t hc2, int conf) { krb5_data msg, wrapped, out; int32_t val; msg.data = "foo"; msg.length = 3; krb5_data_zero(&wrapped); krb5_data_zero(&out); val = encrypt_token(c1, hc1, conf, &msg, &wrapped); if (val) { warnx("encrypt_token failed to host: %s", c1->moniker); return val; } val = decrypt_token(c2, hc2, conf, &wrapped, &out); if (val) { krb5_data_free(&wrapped); warnx("decrypt_token failed to host: %s", c2->moniker); return val; } if (msg.length != out.length) { warnx("decrypted'ed token have wrong length (%lu != %lu)", (unsigned long)msg.length, (unsigned long)out.length); val = GSMERR_ERROR; } else if (memcmp(msg.data, out.data, msg.length) != 0) { warnx("decryptd'ed token have wrong data"); val = GSMERR_ERROR; } krb5_data_free(&wrapped); krb5_data_free(&out); return val; } static int32_t test_wrap_ext(struct client *c1, int32_t hc1, struct client *c2, int32_t hc2, int conf, int bflags) { krb5_data header, msg, trailer, wrapped, out; int32_t val; header.data = "header"; header.length = 6; msg.data = "0123456789abcdef"; /* padded for most enctypes */ msg.length = 32; trailer.data = "trailer"; trailer.length = 7; krb5_data_zero(&wrapped); krb5_data_zero(&out); val = wrap_token_ext(c1, hc1, conf, bflags, &header, &msg, &trailer, &wrapped); if (val) { warnx("encrypt_token failed to host: %s", c1->moniker); return val; } val = unwrap_token_ext(c2, hc2, conf, bflags, &header, &wrapped, &trailer, &out); if (val) { krb5_data_free(&wrapped); warnx("decrypt_token failed to host: %s", c2->moniker); return val; } if (msg.length != out.length) { warnx("decrypted'ed token have wrong length (%lu != %lu)", (unsigned long)msg.length, (unsigned long)out.length); val = GSMERR_ERROR; } else if (memcmp(msg.data, out.data, msg.length) != 0) { warnx("decryptd'ed token have wrong data"); val = GSMERR_ERROR; } krb5_data_free(&wrapped); krb5_data_free(&out); return val; } static int32_t test_token(struct client *c1, int32_t hc1, struct client *c2, int32_t hc2, int wrap_ext) { int32_t val; int i; for (i = 0; i < 10; i++) { /* mic */ test_mic(c1, hc1, c2, hc2); test_mic(c2, hc2, c1, hc1); /* wrap */ val = test_wrap(c1, hc1, c2, hc2, 0); if (val) return val; val = test_wrap(c2, hc2, c1, hc1, 0); if (val) return val; val = test_wrap(c1, hc1, c2, hc2, 1); if (val) return val; val = test_wrap(c2, hc2, c1, hc1, 1); if (val) return val; if (wrap_ext) { /* wrap ext */ val = test_wrap_ext(c1, hc1, c2, hc2, 1, 0); if (val) return val; val = test_wrap_ext(c2, hc2, c1, hc1, 1, 0); if (val) return val; val = test_wrap_ext(c1, hc1, c2, hc2, 1, 1); if (val) return val; val = test_wrap_ext(c2, hc2, c1, hc1, 1, 1); if (val) return val; val = test_wrap_ext(c1, hc1, c2, hc2, 0, 0); if (val) return val; val = test_wrap_ext(c2, hc2, c1, hc1, 0, 0); if (val) return val; val = test_wrap_ext(c1, hc1, c2, hc2, 0, 1); if (val) return val; val = test_wrap_ext(c2, hc2, c1, hc1, 0, 1); if (val) return val; } } return GSMERR_OK; } static int log_function(void *ptr) { struct client *c = ptr; int32_t cmd, line; char *file, *string; while (1) { if (krb5_ret_int32(c->logsock, &cmd)) goto out; switch (cmd) { case eLogSetMoniker: if (krb5_ret_string(c->logsock, &file)) goto out; free(file); break; case eLogInfo: case eLogFailure: if (krb5_ret_string(c->logsock, &file)) goto out; if (krb5_ret_int32(c->logsock, &line)) goto out; if (krb5_ret_string(c->logsock, &string)) goto out; printf("%s:%lu: %s\n", file, (unsigned long)line, string); fprintf(logfile, "%s:%lu: %s\n", file, (unsigned long)line, string); fflush(logfile); free(file); free(string); if (krb5_store_int32(c->logsock, 0)) goto out; break; default: errx(1, "client send bad log command: %d", (int)cmd); } } out: return 0; } static void connect_client(const char *slave) { char *name, *port; struct client *c = ecalloc(1, sizeof(*c)); struct addrinfo hints, *res0, *res; int ret, fd; name = estrdup(slave); port = strchr(name, ':'); if (port == NULL) errx(1, "port missing from %s", name); *port++ = 0; c->name = estrdup(slave); memset(&hints, 0, sizeof(hints)); hints.ai_family = PF_UNSPEC; hints.ai_socktype = SOCK_STREAM; ret = getaddrinfo(name, port, &hints, &res0); if (ret) errx(1, "error resolving %s", name); for (res = res0, fd = -1; res; res = res->ai_next) { fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol); if (fd < 0) continue; if (connect(fd, res->ai_addr, res->ai_addrlen) < 0) { close(fd); fd = -1; continue; } c->sa = ecalloc(1, res->ai_addrlen); memcpy(c->sa, res->ai_addr, res->ai_addrlen); c->salen = res->ai_addrlen; break; /* okay we got one */ } if (fd < 0) err(1, "connect to host: %s", name); freeaddrinfo(res); c->sock = krb5_storage_from_fd(fd); close(fd); if (c->sock == NULL) errx(1, "krb5_storage_from_fd"); { int32_t version; char *str = NULL; get_version_capa(c, &version, &c->capabilities, &str); if (str) { free(str); } if (c->capabilities & HAS_MONIKER) get_moniker(c, &c->moniker); else c->moniker = c->name; if (c->capabilities & ISSERVER) get_targetname(c, &c->target_name); } if (logfile) { int fd; printf("starting log socket to client %s\n", c->moniker); fd = wait_log(c); c->logsock = krb5_storage_from_fd(fd); close(fd); if (c->logsock == NULL) errx(1, "failed to create log krb5_storage"); #ifdef ENABLE_PTHREAD_SUPPORT pthread_create(&c->thr, NULL, log_function, c); #else c->child = fork(); if (c->child == -1) errx(1, "failed to fork"); else if (c->child == 0) { log_function(c); fclose(logfile); exit(0); } #endif } clients = erealloc(clients, (num_clients + 1) * sizeof(*clients)); clients[num_clients] = c; num_clients++; free(name); } static struct client * get_client(const char *slave) { size_t i; for (i = 0; i < num_clients; i++) if (strcmp(slave, clients[i]->name) == 0) return clients[i]; errx(1, "failed to find client %s", slave); } /* * */ static int version_flag; static int help_flag; static int wrap_ext = 0; static char *logfile_str; static getarg_strings principals; static getarg_strings slaves; struct getargs args[] = { { "principals", 0, arg_strings, &principals, "Test principal", NULL }, { "slaves", 0, arg_strings, &slaves, "Slaves", NULL }, { "log-file", 0, arg_string, &logfile_str, "Logfile", NULL }, { "wrap-ext", 0, arg_flag, &wrap_ext, "test wrap extended", NULL }, { "version", 0, arg_flag, &version_flag, "Print version", NULL }, { "help", 0, arg_flag, &help_flag, NULL, NULL } }; static void usage(int ret) { arg_printusage (args, sizeof(args) / sizeof(args[0]), NULL, ""); exit (ret); } int main(int argc, char **argv) { int optidx= 0; char *user; char *password; char ***list, **p; size_t num_list, i, j, k; int failed = 0; setprogname (argv[0]); if (getarg (args, sizeof(args) / sizeof(args[0]), argc, argv, &optidx)) usage (1); if (help_flag) usage (0); if (version_flag) { print_version (NULL); return 0; } if (optidx != argc) usage (1); if (principals.num_strings == 0) errx(1, "no principals"); user = estrdup(principals.strings[0]); password = strchr(user, ':'); if (password == NULL) errx(1, "password missing from %s", user); *password++ = 0; if (slaves.num_strings == 0) errx(1, "no principals"); if (logfile_str) { printf("open logfile %s\n", logfile_str); logfile = fopen(logfile_str, "w+"); if (logfile == NULL) err(1, "failed to open: %s", logfile_str); } /* * */ list = permutate_all(&slaves, &num_list); /* * Set up connection to all clients */ printf("Connecting to slaves\n"); for (i = 0; i < slaves.num_strings; i++) connect_client(slaves.strings[i]); /* * Test acquire credentials */ printf("Test acquire credentials\n"); for (i = 0; i < slaves.num_strings; i++) { int32_t hCred, val; val = acquire_cred(clients[i], user, password, 1, &hCred); if (val != GSMERR_OK) { warnx("Failed to acquire_cred on host %s: %d", clients[i]->moniker, (int)val); failed = 1; } else toast_resource(clients[i], hCred); } if (failed) goto out; /* * First test if all slaves can build context to them-self. */ printf("Self context tests\n"); for (i = 0; i < num_clients; i++) { int32_t hCred, val, delegCred; int32_t clientC, serverC; struct client *c = clients[i]; if (c->target_name == NULL) continue; printf("%s connects to self using %s\n", c->moniker, c->target_name); val = acquire_cred(c, user, password, 1, &hCred); if (val != GSMERR_OK) errx(1, "failed to acquire_cred: %d", (int)val); val = build_context(c, c, GSS_C_REPLAY_FLAG|GSS_C_SEQUENCE_FLAG| GSS_C_INTEG_FLAG|GSS_C_CONF_FLAG| GSS_C_DELEG_FLAG|GSS_C_MUTUAL_FLAG, hCred, &clientC, &serverC, &delegCred); if (val == GSMERR_OK) { test_token(c, clientC, c, serverC, wrap_ext); toast_resource(c, clientC); toast_resource(c, serverC); if (delegCred) toast_resource(c, delegCred); } else { warnx("build_context failed: %d", (int)val); } /* * */ val = build_context(c, c, GSS_C_INTEG_FLAG|GSS_C_CONF_FLAG, hCred, &clientC, &serverC, &delegCred); if (val == GSMERR_OK) { test_token(c, clientC, c, serverC, wrap_ext); toast_resource(c, clientC); toast_resource(c, serverC); if (delegCred) toast_resource(c, delegCred); } else { warnx("build_context failed: %d", (int)val); } toast_resource(c, hCred); } /* * Build contexts though all entries in each lists, including the * step from the last entry to the first, ie treat the list as a * circle. * * Only follow the delegated credential, but test "all" * flags. (XXX only do deleg|mutual right now. */ printf("\"All\" permutation tests\n"); for (i = 0; i < num_list; i++) { int32_t hCred, val, delegCred = 0; int32_t clientC = 0, serverC = 0; struct client *client, *server; p = list[i]; client = get_client(p[0]); val = acquire_cred(client, user, password, 1, &hCred); if (val != GSMERR_OK) errx(1, "failed to acquire_cred: %d", (int)val); for (j = 1; j < num_clients + 1; j++) { server = get_client(p[j % num_clients]); if (server->target_name == NULL) break; for (k = 1; k < j; k++) printf("\t"); printf("%s -> %s\n", client->moniker, server->moniker); val = build_context(client, server, GSS_C_REPLAY_FLAG|GSS_C_SEQUENCE_FLAG| GSS_C_INTEG_FLAG|GSS_C_CONF_FLAG| GSS_C_DELEG_FLAG|GSS_C_MUTUAL_FLAG, hCred, &clientC, &serverC, &delegCred); if (val != GSMERR_OK) { warnx("build_context failed: %d", (int)val); break; } val = test_token(client, clientC, server, serverC, wrap_ext); if (val) break; toast_resource(client, clientC); toast_resource(server, serverC); if (!delegCred) { warnx("no delegated cred on %s", server->moniker); break; } toast_resource(client, hCred); hCred = delegCred; client = server; } if (hCred) toast_resource(client, hCred); } /* * Close all connections to clients */ out: printf("sending goodbye and waiting for log sockets\n"); for (i = 0; i < num_clients; i++) { goodbye(clients[i]); if (clients[i]->logsock) { #ifdef ENABLE_PTHREAD_SUPPORT pthread_join(&clients[i]->thr, NULL); #else waitpid(clients[i]->child, NULL, 0); #endif } } printf("done\n"); return 0; }