/* SSSD LDAP Backend Module -- prime ccache with TGT in a child process Authors: Jakub Hrozek Copyright (C) 2009 Red Hat This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include #include #include #include #include "util/util.h" #include "util/sss_krb5.h" #include "providers/child_common.h" #include "providers/dp_backend.h" static krb5_context krb5_error_ctx; struct input_buffer { const char *realm_str; const char *princ_str; const char *keytab_name; }; static errno_t unpack_buffer(uint8_t *buf, size_t size, struct input_buffer *ibuf) { size_t p = 0; uint32_t *len; /* realm_str size and length */ DEBUG(7, ("total buffer size: %d\n", size)); if ((p + sizeof(uint32_t)) > size) { DEBUG(1, ("Error: buffer too big!\n")); return EINVAL; } len = ((uint32_t *)(buf+p)); p += sizeof(uint32_t); DEBUG(7, ("realm_str size: %d\n", *len)); if (*len) { if ((p + *len ) > size) return EINVAL; ibuf->realm_str = (char *) copy_buffer_and_add_zero(ibuf, buf+p, sizeof(char) * (*len)); DEBUG(7, ("got realm_str: %s\n", ibuf->realm_str)); if (ibuf->realm_str == NULL) return ENOMEM; p += *len; } /* princ_str size and length */ if ((p + sizeof(uint32_t)) > size) return EINVAL; len = ((uint32_t *)(buf+p)); p += sizeof(uint32_t); DEBUG(7, ("princ_str size: %d\n", *len)); if (*len) { if ((p + *len ) > size) return EINVAL; ibuf->princ_str = (char *) copy_buffer_and_add_zero(ibuf, buf+p, sizeof(char) * (*len)); DEBUG(7, ("got princ_str: %s\n", ibuf->princ_str)); if (ibuf->princ_str == NULL) return ENOMEM; p += *len; } /* keytab_name size and length */ if ((p + sizeof(uint32_t)) > size) return EINVAL; len = ((uint32_t *)(buf+p)); p += sizeof(uint32_t); DEBUG(7, ("keytab_name size: %d\n", *len)); if (*len) { if ((p + *len ) > size) return EINVAL; ibuf->keytab_name = (char *) copy_buffer_and_add_zero(ibuf, buf+p, sizeof(char) * (*len)); DEBUG(7, ("got keytab_name: %s\n", ibuf->keytab_name)); if (ibuf->keytab_name == NULL) return ENOMEM; p += *len; } return EOK; } static int pack_buffer(struct response *r, int result, const char *msg) { int len; int p = 0; len = strlen(msg); r->size = 2 * sizeof(uint32_t) + len; /* result */ ((uint32_t *)(&r->buf[p]))[0] = result; p += sizeof(uint32_t); /* message size */ ((uint32_t *)(&r->buf[p]))[0] = len; p += sizeof(uint32_t); /* message itself */ memcpy(&r->buf[p], msg, len); p += len; return EOK; } static int ldap_child_get_tgt_sync(TALLOC_CTX *memctx, const char *realm_str, const char *princ_str, const char *keytab_name, const char **ccname_out) { char *ccname; char *realm_name = NULL; char *full_princ = NULL; krb5_context context = NULL; krb5_keytab keytab = NULL; krb5_ccache ccache = NULL; krb5_principal kprinc; krb5_creds my_creds; krb5_get_init_creds_opt options; krb5_error_code krberr; int ret; krberr = krb5_init_context(&context); if (krberr) { DEBUG(2, ("Failed to init kerberos context\n")); return EFAULT; } if (!realm_str) { krberr = krb5_get_default_realm(context, &realm_name); if (krberr) { DEBUG(2, ("Failed to get default realm name: %s\n", sss_krb5_get_error_message(context, krberr))); ret = EFAULT; goto done; } } else { realm_name = talloc_strdup(memctx, realm_str); if (!realm_name) { ret = ENOMEM; goto done; } } if (princ_str) { if (!strchr(princ_str, '@')) { full_princ = talloc_asprintf(memctx, "%s@%s", princ_str, realm_name); } else { full_princ = talloc_strdup(memctx, princ_str); } } else { char hostname[512]; ret = gethostname(hostname, 511); if (ret == -1) { ret = errno; goto done; } hostname[511] = '\0'; full_princ = talloc_asprintf(memctx, "host/%s@%s", hostname, realm_name); } if (!full_princ) { ret = ENOMEM; goto done; } DEBUG(4, ("Principal name is: [%s]\n", full_princ)); krberr = krb5_parse_name(context, full_princ, &kprinc); if (krberr) { DEBUG(2, ("Unable to build principal: %s\n", sss_krb5_get_error_message(context, krberr))); ret = EFAULT; goto done; } if (keytab_name) { krberr = krb5_kt_resolve(context, keytab_name, &keytab); } else { krberr = krb5_kt_default(context, &keytab); } if (krberr) { DEBUG(2, ("Failed to read keytab file: %s\n", sss_krb5_get_error_message(context, krberr))); ret = EFAULT; goto done; } ccname = talloc_asprintf(memctx, "FILE:%s/ccache_%s", DB_PATH, realm_name); if (!ccname) { ret = ENOMEM; goto done; } krberr = krb5_cc_resolve(context, ccname, &ccache); if (krberr) { DEBUG(2, ("Failed to set cache name: %s\n", sss_krb5_get_error_message(context, krberr))); ret = EFAULT; goto done; } memset(&my_creds, 0, sizeof(my_creds)); memset(&options, 0, sizeof(options)); krb5_get_init_creds_opt_set_address_list(&options, NULL); krb5_get_init_creds_opt_set_forwardable(&options, 0); krb5_get_init_creds_opt_set_proxiable(&options, 0); /* set a very short lifetime, we don't keep the ticket around */ krb5_get_init_creds_opt_set_tkt_life(&options, 300); krberr = krb5_get_init_creds_keytab(context, &my_creds, kprinc, keytab, 0, NULL, &options); if (krberr) { DEBUG(2, ("Failed to init credentials: %s\n", sss_krb5_get_error_message(context, krberr))); ret = EFAULT; goto done; } krberr = krb5_cc_initialize(context, ccache, kprinc); if (krberr) { DEBUG(2, ("Failed to init ccache: %s\n", sss_krb5_get_error_message(context, krberr))); ret = EFAULT; goto done; } krberr = krb5_cc_store_cred(context, ccache, &my_creds); if (krberr) { DEBUG(2, ("Failed to store creds: %s\n", sss_krb5_get_error_message(context, krberr))); ret = EFAULT; goto done; } ret = EOK; *ccname_out = ccname; done: if (keytab) krb5_kt_close(context, keytab); if (context) krb5_free_context(context); return ret; } static int prepare_response(TALLOC_CTX *mem_ctx, const char *ccname, krb5_error_code kerr, struct response **rsp) { int ret; struct response *r = NULL; const char *krb5_msg = NULL; r = talloc_zero(mem_ctx, struct response); if (!r) return ENOMEM; r->buf = talloc_size(mem_ctx, MAX_CHILD_MSG_SIZE); if (r->buf == NULL) { DEBUG(1, ("talloc_size failed.\n")); return ENOMEM; } r->max_size = MAX_CHILD_MSG_SIZE; r->size = 0; if (kerr == 0) { ret = pack_buffer(r, EOK, ccname); } else { krb5_msg = sss_krb5_get_error_message(krb5_error_ctx, kerr); if (krb5_msg == NULL) { DEBUG(1, ("sss_krb5_get_error_message failed.\n")); return ENOMEM; } ret = pack_buffer(r, EFAULT, krb5_msg); sss_krb5_free_error_message(krb5_error_ctx, krb5_msg); } if (ret != EOK) { DEBUG(1, ("pack_buffer failed\n")); return ret; } *rsp = r; return EOK; } int main(int argc, const char *argv[]) { int ret; int kerr; int opt; int debug_fd = -1; poptContext pc; TALLOC_CTX *main_ctx; uint8_t *buf = NULL; ssize_t len = 0; const char *ccname = NULL; struct input_buffer *ibuf = NULL; struct response *resp = NULL; struct poptOption long_options[] = { POPT_AUTOHELP {"debug-level", 'd', POPT_ARG_INT, &debug_level, 0, "Debug level", NULL}, {"debug-timestamps", 0, POPT_ARG_NONE, &debug_timestamps, 0, "Add debug timestamps", NULL}, {"debug-fd", 0, POPT_ARG_INT, &debug_fd, 0, "Add debug timestamps", NULL}, POPT_TABLEEND }; pc = poptGetContext(argv[0], argc, argv, long_options, 0); while((opt = poptGetNextOpt(pc)) != -1) { switch(opt) { default: fprintf(stderr, "\nInvalid option %s: %s\n\n", poptBadOption(pc, 0), poptStrerror(opt)); poptPrintUsage(pc, stderr, 0); _exit(-1); } } poptFreeContext(pc); main_ctx = talloc_new(NULL); if (main_ctx == NULL) { DEBUG(1, ("talloc_new failed.\n")); _exit(-1); } debug_prg_name = talloc_asprintf(main_ctx, "[sssd[ldap_child[%d]]]", getpid()); if (debug_fd != -1) { ret = set_debug_file_from_fd(debug_fd); if (ret != EOK) { DEBUG(1, ("set_debug_file_from_fd failed.\n")); } } buf = talloc_size(main_ctx, sizeof(uint8_t)*IN_BUF_SIZE); if (buf == NULL) { DEBUG(1, ("talloc_size failed.\n")); goto fail; } ibuf = talloc_zero(main_ctx, struct input_buffer); if (ibuf == NULL) { DEBUG(1, ("talloc_size failed.\n")); goto fail; } while ((ret = read(STDIN_FILENO, buf + len, IN_BUF_SIZE - len)) != 0) { if (ret == -1) { if (errno == EINTR || errno == EAGAIN) { continue; } DEBUG(1, ("read failed [%d][%s].\n", errno, strerror(errno))); goto fail; } else if (ret > 0) { len += ret; if (len > IN_BUF_SIZE) { DEBUG(1, ("read too much, this should never happen.\n")); goto fail; } continue; } else { DEBUG(1, ("unexpected return code of read [%d].\n", ret)); goto fail; } } close(STDIN_FILENO); ret = unpack_buffer(buf, len, ibuf); if (ret != EOK) { DEBUG(1, ("unpack_buffer failed.[%d][%s].\n", ret, strerror(ret))); goto fail; } kerr = ldap_child_get_tgt_sync(main_ctx, ibuf->realm_str, ibuf->princ_str, ibuf->keytab_name, &ccname); if (kerr != EOK) { DEBUG(1, ("ldap_child_get_tgt_sync failed.\n")); /* Do not return, must report failure */ } ret = prepare_response(main_ctx, ccname, kerr, &resp); if (ret != EOK) { DEBUG(1, ("prepare_response failed. [%d][%s].\n", ret, strerror(ret))); return ENOMEM; } ret = write(STDOUT_FILENO, resp->buf, resp->size); if (ret == -1) { ret = errno; DEBUG(1, ("write failed [%d][%s].\n", ret, strerror(ret))); return errno; } close(STDOUT_FILENO); talloc_free(main_ctx); _exit(0); fail: close(STDOUT_FILENO); talloc_free(main_ctx); _exit(-1); }