diff options
Diffstat (limited to 'ipa-client')
-rw-r--r-- | ipa-client/Makefile.am | 30 | ||||
-rw-r--r-- | ipa-client/configure.ac | 125 | ||||
-rwxr-xr-x | ipa-client/ipa-client.spec | 1 | ||||
-rw-r--r-- | ipa-client/ipa-client.spec.in | 1 | ||||
-rw-r--r-- | ipa-client/ipa-getkeytab.c | 495 |
5 files changed, 652 insertions, 0 deletions
diff --git a/ipa-client/Makefile.am b/ipa-client/Makefile.am index caabffe15..95c2cc3e3 100644 --- a/ipa-client/Makefile.am +++ b/ipa-client/Makefile.am @@ -4,6 +4,36 @@ AUTOMAKE_OPTIONS = 1.7 NULL = +INCLUDES = \ + -I. \ + -I$(srcdir) \ + -DPREFIX=\""$(prefix)"\" \ + -DBINDIR=\""$(bindir)"\" \ + -DLIBDIR=\""$(libdir)"\" \ + -DLIBEXECDIR=\""$(libexecdir)"\" \ + -DDATADIR=\""$(datadir)"\" \ + $(KRB5_CFLAGS) \ + $(LDAP_CFLAGS) \ + $(SASL_CFLAGS) \ + $(POPT_CFLAGS) \ + $(WARN_CFLAGS) \ + $(NULL) + +sbin_PROGRAMS = \ + ipa-getkeytab \ + $(NULL) + +ipa_getkeytab_SOURCES = \ + ipa-getkeytab.c \ + $(NULL) + +ipa_getkeytab_LDADD = \ + $(KRB5_LIBS) \ + $(LDAP_LIBS) \ + $(SASL_LIBS) \ + $(POPT_LIBS) \ + $(NULL) + SUBDIRS = \ firefox \ ipaclient \ diff --git a/ipa-client/configure.ac b/ipa-client/configure.ac index b50928dab..655c0cc1c 100644 --- a/ipa-client/configure.ac +++ b/ipa-client/configure.ac @@ -10,9 +10,134 @@ AM_INIT_AUTOMAKE AM_MAINTAINER_MODE +AC_PROG_CC +AC_STDC_HEADERS +AC_DISABLE_STATIC +AC_PROG_LIBTOOL + +AC_HEADER_STDC + AC_SUBST(VERSION) dnl --------------------------------------------------------------------------- +dnl - Check for KRB5 +dnl --------------------------------------------------------------------------- + +KRB5_LIBS= +AC_CHECK_HEADER(krb5.h) + +krb5_impl=mit + +if test "x$ac_cv_header_krb5_h" = "xyes" ; then + dnl lazy check for Heimdal Kerberos + AC_CHECK_HEADERS(heim_err.h) + if test $ac_cv_header_heim_err_h = yes ; then + krb5_impl=heimdal + else + krb5_impl=mit + fi + + if test "x$krb5_impl" = "xmit"; then + AC_CHECK_LIB(k5crypto, main, + [krb5crypto=k5crypto], + [krb5crypto=crypto]) + + AC_CHECK_LIB(krb5, main, + [have_krb5=yes + KRB5_LIBS="-lkrb5 -l$krb5crypto -lcom_err"], + [have_krb5=no], + [-l$krb5crypto -lcom_err]) + + elif test "x$krb5_impl" = "xheimdal"; then + AC_CHECK_LIB(des, main, + [krb5crypto=des], + [krb5crypto=crypto]) + + AC_CHECK_LIB(krb5, main, + [have_krb5=yes + KRB5_LIBS="-lkrb5 -l$krb5crypto -lasn1 -lroken -lcom_err"], + [have_krb5=no], + [-l$krb5crypto -lasn1 -lroken -lcom_err]) + + AC_DEFINE(HAVE_HEIMDAL_KERBEROS, 1, + [define if you have HEIMDAL Kerberos]) + + else + have_krb5=no + AC_MSG_WARN([Unrecognized Kerberos5 Implementation]) + fi + + if test "x$have_krb5" = "xyes" ; then + ol_link_krb5=yes + + AC_DEFINE(HAVE_KRB5, 1, + [define if you have Kerberos V]) + + else + AC_MSG_ERROR([Required Kerberos 5 support not available]) + fi + +fi + +AC_SUBST(KRB5_LIBS) + +dnl --------------------------------------------------------------------------- +dnl - Check for LDAP +dnl --------------------------------------------------------------------------- + +LDAP_LIBS= +AC_CHECK_HEADER(ldap.h) +AC_CHECK_HEADER(lber.h) + +AC_CHECK_LIB(ldap, ldap_search, with_ldap=yes) +dnl Check for other libraries we need to link with to get the main routines. +test "$with_ldap" != "yes" && { AC_CHECK_LIB(ldap, ldap_open, [with_ldap=yes with_ldap_lber=yes], , -llber) } +test "$with_ldap" != "yes" && { AC_CHECK_LIB(ldap, ldap_open, [with_ldap=yes with_ldap_lber=yes with_ldap_krb=yes], , -llber -lkrb) } +test "$with_ldap" != "yes" && { AC_CHECK_LIB(ldap, ldap_open, [with_ldap=yes with_ldap_lber=yes with_ldap_krb=yes with_ldap_des=yes], , -llber -lkrb -ldes) } +dnl Recently, we need -lber even though the main routines are elsewhere, +dnl because otherwise be get link errors w.r.t. ber_pvt_opt_on. So just +dnl check for that (it's a variable not a fun but that doesn't seem to +dnl matter in these checks) and stick in -lber if so. Can't hurt (even to +dnl stick it in always shouldn't hurt, I don't think) ... #### Someone who +dnl #### understands LDAP needs to fix this properly. +test "$with_ldap_lber" != "yes" && { AC_CHECK_LIB(lber, ber_pvt_opt_on, with_ldap_lber=yes) } + +if test "$with_ldap" = "yes"; then + if test "$with_ldap_des" = "yes" ; then + LDAP_LIBS="${LDAP_LIBS} -ldes" + fi + if test "$with_ldap_krb" = "yes" ; then + LDAP_LIBS="${LDAP_LIBS} -lkrb" + fi + if test "$with_ldap_lber" = "yes" ; then + LDAP_LIBS="${LDAP_LIBS} -llber" + fi + LDAP_LIBS="${LDAP_LIBS} -lldap" +else + AC_MSG_ERROR([LDAP not found]) +fi + +AC_SUBST(LDAP_LIBS) + +dnl --------------------------------------------------------------------------- +dnl - Check for POPT +dnl --------------------------------------------------------------------------- + +POPT_LIBS= +AC_CHECK_HEADER(popt.h) +AC_CHECK_LIB(popt, poptGetContext, [POPT_LIBS="-lpopt"]) +AC_SUBST(POPT_LIBS) + +dnl --------------------------------------------------------------------------- +dnl - Check for SASL +dnl --------------------------------------------------------------------------- + +SASL_LIBS= +AC_CHECK_HEADER(sasl/sasl.h) +AC_CHECK_LIB(sasl2, sasl_client_init, [SASL_LIBS="-lsasl2"]) +AC_SUBST(SASL_LIBS) + +dnl --------------------------------------------------------------------------- dnl - Check for Python dnl --------------------------------------------------------------------------- diff --git a/ipa-client/ipa-client.spec b/ipa-client/ipa-client.spec index c184600bb..9c7f41b59 100755 --- a/ipa-client/ipa-client.spec +++ b/ipa-client/ipa-client.spec @@ -36,6 +36,7 @@ rm -rf %{buildroot} %files %defattr(-,root,root,-) %{_sbindir}/ipa-client-install +%{_sbindir}/ipa-getkeytab %dir %{_usr}/share/ipa %{_usr}/share/ipa/* diff --git a/ipa-client/ipa-client.spec.in b/ipa-client/ipa-client.spec.in index 5dbd90f3f..47964a4cc 100644 --- a/ipa-client/ipa-client.spec.in +++ b/ipa-client/ipa-client.spec.in @@ -36,6 +36,7 @@ rm -rf %{buildroot} %files %defattr(-,root,root,-) %{_sbindir}/ipa-client-install +%{_sbindir}/ipa-getkeytab %dir %{_usr}/share/ipa %{_usr}/share/ipa/* diff --git a/ipa-client/ipa-getkeytab.c b/ipa-client/ipa-getkeytab.c new file mode 100644 index 000000000..c032aa1dd --- /dev/null +++ b/ipa-client/ipa-getkeytab.c @@ -0,0 +1,495 @@ +/* (C) Simo Sorce */ +#define _GNU_SOURCE + +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <sys/time.h> +#include <unistd.h> +#include <stdio.h> +#include <stdarg.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <time.h> +#include <krb5.h> +#include <ldap.h> +#include <sasl/sasl.h> +#include <popt.h> + +static int ldap_sasl_interact(LDAP *ld, unsigned flags, void *priv_data, void *sit) +{ + sasl_interact_t *in = NULL; + int ret = LDAP_OTHER; + krb5_principal princ = (krb5_principal)priv_data; + + if (!ld) return LDAP_PARAM_ERROR; + + for (in = sit; in && in->id != SASL_CB_LIST_END; in++) { + switch(in->id) { + case SASL_CB_USER: + in->result = princ->data[0].data; + in->len = princ->data[0].length; + ret = LDAP_SUCCESS; + break; + case SASL_CB_GETREALM: + in->result = princ->realm.data; + in->len = princ->realm.length; + ret = LDAP_SUCCESS; + break; + default: + in->result = NULL; + in->len = 0; + ret = LDAP_OTHER; + } + } + return ret; +} + +#define KEYTAB_SET_OID "2.16.840.1.113730.3.8.3.1" +#define KEYTAB_RET_OID "2.16.840.1.113730.3.8.3.2" + +static void free_keys(krb5_context krbctx, krb5_keyblock *keys, int num_keys) +{ + int i; + + for (i = 0; i < num_keys; i++) { + krb5_free_keyblock_contents(krbctx, &keys[i]); + } + free(keys); +} + +static int create_keys(krb5_context krbctx, krb5_keyblock **keys) +{ + krb5_error_code krberr; + krb5_enctype *ktypes; + krb5_keyblock *key; + int i, j, k, max_keys; + + krberr = krb5_get_permitted_enctypes(krbctx, &ktypes); + if (krberr) { + fprintf(stderr, "No preferred enctypes ?!\n"); + return 0; + } + + for (i = 0; ktypes[i]; i++) /* count max encodings */ ; + max_keys = i; + if (!max_keys) { + krb5_free_ktypes(krbctx, ktypes); + fprintf(stderr, "No preferred enctypes ?!\n"); + return 0; + } + + key = calloc(max_keys, sizeof(krb5_keyblock)); + if (!key) { + krb5_free_ktypes(krbctx, ktypes); + fprintf(stderr, "Out of Memory!\n"); + return 0; + } + + k = 0; /* effective number of keys */ + + for (i = 0; i < max_keys; i++) { + krb5_boolean similar; + + /* Check we don't already have a key with a similar encoding, + * it would just produce redundant data and this is what the + * kerberos libs do anyway */ + similar = 0; + for (j = 0; j < i; j++) { + krberr = krb5_c_enctype_compare(krbctx, ktypes[i], + ktypes[j], &similar); + if (krberr) { + krb5_free_ktypes(krbctx, ktypes); + free_keys(krbctx, key, i); + fprintf(stderr, "Enctype comparison failed!\n"); + return 0; + } + if (similar) break; + } + if (similar) continue; + + krberr = krb5_c_make_random_key(krbctx, ktypes[i], &key[k]); + if (krberr) { + krb5_free_ktypes(krbctx, ktypes); + free_keys(krbctx, key, k); + fprintf(stderr, "Making random key failed!\n"); + return 0; + } + k++; + } + + krb5_free_ktypes(krbctx, ktypes); + + *keys = key; + return k; +} + +static struct berval *create_key_control(krb5_keyblock *keys, int num_keys, const char *principalName) +{ + struct berval *bval; + BerElement *be; + int ret, i; + + be = ber_alloc_t(LBER_USE_DER); + if (!be) { + return NULL; + } + + ret = ber_printf(be, "{s{", principalName); + if (ret == -1) { + ber_free(be, 1); + return NULL; + } + + for (i = 0; i < num_keys; i++) { + + /* we set only the EncryptionKey, no salt or s2kparams */ + ret = ber_printf(be, "{t[{t[i]t[o]}]}", + (ber_tag_t)(LBER_CONSTRUCTED | LBER_CLASS_CONTEXT | 0), + (ber_tag_t)(LBER_CONSTRUCTED | LBER_CLASS_CONTEXT | 0), + (ber_int_t)keys[i].enctype, + (ber_tag_t)(LBER_CONSTRUCTED | LBER_CLASS_CONTEXT | 1), + (char *)keys[i].contents, (ber_len_t)keys[i].length); + + if (ret == -1) { + ber_free(be, 1); + return NULL; + } + } + + ret = ber_printf(be, "}}"); + if (ret == -1) { + ber_free(be, 1); + return NULL; + } + + ret = ber_flatten(be, &bval); + if (ret == -1) { + ber_free(be, 1); + return NULL; + } + + ber_free(be, 1); + return bval; +} + +int filter_keys(krb5_context krbctx, krb5_keyblock *keys, int *num_keys, ber_int_t *enctypes) +{ + int ret, i, j, k; + + k = *num_keys; + + for (i = 0; i < k; i++) { + for (j = 0; enctypes[j]; j++) { + if (keys[i].enctype == enctypes[j]) break; + } + if (enctypes[j] == 0) { /* unsupported one */ + krb5_free_keyblock_contents(krbctx, &keys[i]); + /* remove unsupported one */ + k--; + for (j = i; j < k; j++) { + keys[j] = keys[j + 1]; + } + /* new key has been moved to this position, make sure + * we do not skip it, by neutralizing next i increment */ + i--; + } + } + + if (k == 0) { + return -1; + } + + *num_keys = k; + return 0; +} + +static int ldap_set_keytab(const char *servername, + const char *principal_name, + krb5_principal princ, + krb5_keyblock *keys, + int num_keys, + ber_int_t **enctypes) +{ + int version; + LDAP *ld = NULL; + BerElement *ctrl = NULL; + BerElement *sctrl = NULL; + struct berval *control = NULL; + char *ldap_uri = NULL; + struct berval **ncvals; + char *ldap_base = NULL; + char *retoid = NULL; + struct berval *retdata = NULL; + struct timeval tv; + LDAPMessage *entry, *res = NULL; + LDAPControl **srvctrl = NULL; + LDAPControl *pprc = NULL; + char *err = NULL; + int msgid; + int ret, rc; + int kvno, i; + ber_tag_t rtag; + struct berval bv; + ber_int_t *encs = NULL; + + /* cant' return more than num_keys, sometimes less */ + encs = calloc(num_keys + 1, sizeof(ber_int_t)); + if (!encs) { + fprintf(stderr, "Out of Memory!\n"); + return 0; + } + + /* build password change control */ + control = create_key_control(keys, num_keys, principal_name); + if (!control) { + fprintf(stderr, "Failed to create control!\n"); + goto error_out; + } + + /* connect to ldap server */ + ret = asprintf(&ldap_uri, "ldap://%s:389", servername); + if (ret == -1) { + fprintf(stderr, "Unable to determine server URI!\n"); + goto error_out; + } + + /* TODO: support referrals ? */ + ret = ldap_initialize(&ld, ldap_uri); + if(ret != LDAP_SUCCESS) { + fprintf(stderr, "Unable to initialize ldap library!\n"); + goto error_out; + } + + version = LDAP_VERSION3; + ret = ldap_set_option(ld, LDAP_OPT_PROTOCOL_VERSION, &version); + if (ret != LDAP_OPT_SUCCESS) { + fprintf(stderr, "Unable to set ldap options!\n"); + goto error_out; + } + + ret = ldap_sasl_interactive_bind_s(ld, + NULL, "GSSAPI", + NULL, NULL, + LDAP_SASL_AUTOMATIC, + ldap_sasl_interact, princ); + if (ret != LDAP_SUCCESS) { + fprintf(stderr, "SASL Bind failed!\n"); + goto error_out; + } + + /* find base dn */ + /* TODO: address the case where we have multiple naming contexts */ + tv.tv_sec = 10; + tv.tv_usec = 0; + + /* perform password change */ + ret = ldap_extended_operation(ld, + KEYTAB_SET_OID, + control, NULL, NULL, + &msgid); + if (ret != LDAP_SUCCESS) { + fprintf(stderr, "Operation failed! %s\n", ldap_err2string(ret)); + goto error_out; + } + + ber_bvfree(control); + + tv.tv_sec = 10; + tv.tv_usec = 0; + + ret = ldap_result(ld, msgid, 1, &tv, &res); + if (ret == -1) { + fprintf(stderr, "Operation failed! %s\n", ldap_err2string(ret)); + goto error_out; + } + + ret = ldap_parse_extended_result(ld, res, &retoid, &retdata, 0); + if(ret != LDAP_SUCCESS) { + fprintf(stderr, "Operation failed! %s\n", ldap_err2string(ret)); + goto error_out; + } + + ret = ldap_parse_result(ld, res, &rc, NULL, &err, NULL, &srvctrl, 0); + if(ret != LDAP_SUCCESS || rc != LDAP_SUCCESS) { + fprintf(stderr, "Operation failed! %s\n", err?err:ldap_err2string(ret)); + goto error_out; + } + + if (!srvctrl) { + fprintf(stderr, "Missing reply control!\n"); + goto error_out; + } + + for (i = 0; srvctrl[i]; i++) { + if (0 == strcmp(srvctrl[i]->ldctl_oid, KEYTAB_RET_OID)) { + pprc = srvctrl[i]; + } + } + if (!pprc) { + fprintf(stderr, "Missing reply control!\n"); + goto error_out; + } + + sctrl = ber_init(&pprc->ldctl_value); + + if (!sctrl) { + fprintf(stderr, "ber_init() failed, Invalid control ?!\n"); + goto error_out; + } + + /* Format of response + * + * KeytabGetRequest ::= SEQUENCE { + * new_kvno Int32 + * SEQUENCE OF KeyTypes + * } + * + * * List of accepted enctypes * + * KeyTypes ::= SEQUENCE { + * enctype Int32 + * } + */ + + rtag = ber_scanf(sctrl, "{i{", &kvno); + if (rtag == LBER_ERROR) { + fprintf(stderr, "ber_scanf() failed, Invalid control ?!\n"); + goto error_out; + } + + for (i = 0; i < num_keys; i++) { + ret = ber_scanf(sctrl, "{i}", &encs[i]); + if (ret == LBER_ERROR) break; + } + *enctypes = encs; + + if (err) ldap_memfree(err); + ber_free(sctrl, 1); + ldap_controls_free(srvctrl); + ldap_msgfree(res); + ldap_unbind_ext_s(ld, NULL, NULL); + free(ldap_uri); + return kvno; + +error_out: + if (sctrl) ber_free(sctrl, 1); + if (srvctrl) ldap_controls_free(srvctrl); + if (err) ldap_memfree(err); + if (res) ldap_msgfree(res); + if (ld) ldap_unbind_ext_s(ld, NULL, NULL); + if (ldap_uri) free(ldap_uri); + if (control) ber_bvfree(control); + if (encs) free(encs); + return 0; +} + +int main(int argc, char *argv[]) +{ + static const char *server = NULL; + static const char *principal = NULL; + static const char *keytab = NULL; + struct poptOption options[] = { + { "server", 's', POPT_ARG_STRING, &server, 0, "Contact this specific KDC Server", "Server Name" }, + { "principal", 'p', POPT_ARG_STRING, &principal, 0, "The principal to get a keytab for (ex: ftp/ftp.example.com@EXAMPLE.COM)", "Kerberos Service Principal Name" }, + { "keytab", 'k', POPT_ARG_STRING, &keytab, 0, "File were to store the keytab information", "Keytab File Name" }, + { NULL, 0, POPT_ARG_NONE, NULL, 0, NULL, NULL } + }; + poptContext pc; + char *ktname; + krb5_context krbctx; + krb5_ccache ccache; + krb5_principal uprinc; + krb5_principal sprinc; + krb5_error_code krberr; + krb5_keyblock *keys = NULL; + int num_keys = 0; + ber_int_t *enctypes; + krb5_keytab kt; + int kvno; + int i, ret; + + pc = poptGetContext("ipa-getkeytab", argc, (const char **)argv, options, 0); + ret = poptGetNextOpt(pc); + if (ret != -1 || !server || !principal || !keytab) { + poptPrintUsage(pc, stderr, 0); + exit(1); + } + + ret = asprintf(&ktname, "WRFILE:%s", keytab); + if (ret == -1) { + exit(2); + } + + krberr = krb5_init_context(&krbctx); + if (krberr) { + fprintf(stderr, "Kerberos context initialization failed\n"); + exit(3); + } + + krberr = krb5_parse_name(krbctx, principal, &sprinc); + if (krberr) { + fprintf(stderr, "Invalid Service Principal Name\n"); + exit(4); + } + + krberr = krb5_cc_default(krbctx, &ccache); + if (krberr) { + fprintf(stderr, "Kerberos Credential Cache not found\nDo you have a Kerberos Ticket?\n"); + exit(5); + } + + krberr = krb5_cc_get_principal(krbctx, ccache, &uprinc); + if (krberr) { + fprintf(stderr, "Kerberos User Principal not found\nDo you have a valid Credential Cache?\n"); + exit(6); + } + + krberr = krb5_kt_resolve(krbctx, ktname, &kt); + if (krberr) { + fprintf(stderr, "Failed to open Keytab\n"); + exit(7); + } + + /* create key material */ + num_keys = create_keys(krbctx, &keys); + if (!num_keys) { + fprintf(stderr, "Failed to create random key material\n"); + exit(8); + } + + kvno = ldap_set_keytab(server, principal, uprinc, keys, num_keys, &enctypes); + if (!kvno) { + exit(9); + } + + ret = filter_keys(krbctx, keys, &num_keys, enctypes); + if (ret == -1) { + fprintf(stderr, "No keys accepted by the KDC!\n"); + exit(10); + } + + for (i = 0; i < num_keys; i++) { + krb5_keytab_entry kt_entry; + memset((char *)&kt_entry, 0, sizeof(kt_entry)); + kt_entry.principal = sprinc; + kt_entry.key = keys[i]; + kt_entry.vno = kvno; + + krberr = krb5_kt_add_entry(krbctx, kt, &kt_entry); + if (krberr) { + fprintf(stderr, "Failed to add key to the keytab\n"); + exit (11); + } + } + + free_keys(krbctx, keys, num_keys); + + krberr = krb5_kt_close(krbctx, kt); + if (krberr) { + fprintf(stderr, "Failed to close the keytab\n"); + exit (12); + } + + exit(0); +} |