/* * adcli * * Copyright (C) 2012 Red Hat Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301 USA * * Author: Stef Walter */ #include "config.h" #include "adcli.h" #include "adprivate.h" #include #include #include #include #include #include krb5_error_code _adcli_krb5_build_principal (krb5_context k5, const char *user, const char *realm, krb5_principal *principal) { krb5_error_code code; char *name = NULL; /* Use user if user contains a @-character and add @realm otherwise */ if (strchr (user, '@') == NULL) { if (asprintf (&name, "%s@%s", user, realm) < 0) { return_val_if_reached (ENOMEM); } } code = krb5_parse_name (k5, name != NULL ? name : user, principal); return_val_if_fail (code == 0, code); free (name); return 0; } krb5_error_code _adcli_krb5_keytab_clear (krb5_context k5, krb5_keytab keytab, krb5_boolean (* match_func) (krb5_context, krb5_keytab_entry *, void *), void *match_data) { krb5_kt_cursor cursor; krb5_keytab_entry entry; krb5_error_code code; code = krb5_kt_start_seq_get (k5, keytab, &cursor); if (code == KRB5_KT_END || code == ENOENT) return 0; else if (code != 0) return code; for (;;) { code = krb5_kt_next_entry (k5, keytab, &entry, &cursor); if (code != 0) break; /* See if we should remove this entry */ if (!match_func (k5, &entry, match_data)) { krb5_free_keytab_entry_contents (k5, &entry); continue; } /* * Here we close the cursor, remove the entry and then * start all over again from the beginning. Dumb but works. */ code = krb5_kt_end_seq_get (k5, keytab, &cursor); return_val_if_fail (code == 0, code); code = krb5_kt_remove_entry (k5, keytab, &entry); krb5_free_keytab_entry_contents (k5, &entry); if (code != 0) return code; code = krb5_kt_start_seq_get (k5, keytab, &cursor); return_val_if_fail (code == 0, code); } if (code == KRB5_KT_END) code = 0; krb5_kt_end_seq_get (k5, keytab, &cursor); return code; } static krb5_boolean match_all_entries (krb5_context k5, krb5_keytab_entry *entry, void *data) { return TRUE; } krb5_error_code _adcli_krb5_keytab_clear_all (krb5_context k5, krb5_keytab keytab) { return _adcli_krb5_keytab_clear (k5, keytab, match_all_entries, NULL); } krb5_error_code _adcli_krb5_keytab_enumerate (krb5_context k5, krb5_keytab keytab, krb5_boolean (* match_func) (krb5_context, krb5_keytab_entry *, void *), void *match_data) { krb5_kt_cursor cursor; krb5_keytab_entry entry; krb5_error_code code; code = krb5_kt_start_seq_get (k5, keytab, &cursor); if (code == KRB5_KT_END || code == ENOENT) return 0; else if (code != 0) return code; for (;;) { code = krb5_kt_next_entry (k5, keytab, &entry, &cursor); if (code != 0) break; /* See if we should continue */ if (!match_func (k5, &entry, match_data)) break; } if (code == KRB5_KT_END) code = 0; krb5_kt_end_seq_get (k5, keytab, &cursor); return code; } adcli_result _adcli_krb5_init_context (krb5_context *k5) { krb5_error_code code; code = krb5_init_context (k5); if (code == ENOMEM) { return_unexpected_if_reached (); } else if (code != 0) { _adcli_err ("Failed to create kerberos context: %s", krb5_get_error_message (NULL, code)); return ADCLI_ERR_UNEXPECTED; } return ADCLI_SUCCESS; } adcli_result _adcli_krb5_open_keytab (krb5_context k5, const char *keytab_name, krb5_keytab *keytab) { krb5_error_code code; if (keytab_name && strcmp (keytab_name, "") != 0) { code = krb5_kt_resolve (k5, keytab_name, keytab); if (code != 0) { _adcli_err ("Failed to open keytab: %s: %s", keytab_name, krb5_get_error_message (k5, code)); return ADCLI_ERR_FAIL; } } else { code = krb5_kt_default (k5, keytab); if (code != 0) { _adcli_err ("Failed to open default keytab: %s", krb5_get_error_message (k5, code)); return ADCLI_ERR_FAIL; } } return ADCLI_SUCCESS; } typedef struct { krb5_kvno kvno; krb5_enctype enctype; int matched; } match_enctype_kvno; static krb5_boolean match_enctype_and_kvno (krb5_context k5, krb5_keytab_entry *entry, void *data) { krb5_boolean similar = FALSE; match_enctype_kvno *closure = data; krb5_error_code code; assert (closure->enctype); code = krb5_c_enctype_compare (k5, closure->enctype, entry->key.enctype, &similar); if (code == 0 && entry->vno == closure->kvno && similar) { closure->matched = 1; return 1; } return 0; } static krb5_error_code _adcli_krb5_get_keyblock (krb5_context k5, krb5_keytab keytab, krb5_keyblock *keyblock, krb5_boolean (* match_func) (krb5_context, krb5_keytab_entry *, void *), void *match_data) { krb5_kt_cursor cursor; krb5_keytab_entry entry; krb5_error_code code; code = krb5_kt_start_seq_get (k5, keytab, &cursor); if (code == KRB5_KT_END || code == ENOENT) return 0; else if (code != 0) return code; for (;;) { code = krb5_kt_next_entry (k5, keytab, &entry, &cursor); if (code != 0) break; /* See if we should remove this entry */ if (!match_func (k5, &entry, match_data)) { krb5_free_keytab_entry_contents (k5, &entry); continue; } code = krb5_copy_keyblock_contents (k5, &entry.key, keyblock); krb5_free_keytab_entry_contents (k5, &entry); break; } if (code == KRB5_KT_END) code = 0; krb5_kt_end_seq_get (k5, keytab, &cursor); return code; } krb5_error_code _adcli_krb5_keytab_copy_entries (krb5_context k5, krb5_keytab keytab, krb5_principal principal, krb5_kvno kvno, krb5_enctype *enctypes) { krb5_keytab_entry entry; krb5_error_code code; int i; match_enctype_kvno closure; for (i = 0; enctypes[i] != 0; i++) { closure.kvno = kvno; closure.enctype = enctypes[i]; closure.matched = 0; memset (&entry, 0, sizeof (entry)); code = _adcli_krb5_get_keyblock (k5, keytab, &entry.key, match_enctype_and_kvno, &closure); if (code != 0 || closure.matched == 0) { return code != 0 ? code : ENOKEY; } entry.principal = principal; entry.vno = kvno; code = krb5_kt_add_entry (k5, keytab, &entry); entry.principal = NULL; krb5_free_keytab_entry_contents (k5, &entry); if (code != 0) return code; } return 0; } krb5_error_code _adcli_krb5_keytab_add_entries (krb5_context k5, krb5_keytab keytab, krb5_principal principal, krb5_kvno kvno, krb5_data *password, krb5_enctype *enctypes, krb5_data *salt) { krb5_keytab_entry entry; krb5_error_code code; int i; for (i = 0; enctypes[i] != 0; i++) { memset (&entry, 0, sizeof(entry)); code = krb5_c_string_to_key (k5, enctypes[i], password, salt, &entry.key); if (code != 0) return code; entry.principal = principal; entry.vno = kvno; code = krb5_kt_add_entry (k5, keytab, &entry); entry.principal = NULL; krb5_free_keytab_entry_contents (k5, &entry); if (code != 0) return code; } return 0; } krb5_error_code _adcli_krb5_keytab_test_salt (krb5_context k5, krb5_keytab scratch, krb5_principal principal, krb5_kvno kvno, krb5_data *password, krb5_enctype *enctypes, krb5_data *salt) { krb5_error_code code; krb5_creds creds; code = _adcli_krb5_keytab_clear_all (k5, scratch); return_val_if_fail (code == 0, code); code = _adcli_krb5_keytab_add_entries (k5, scratch, principal, kvno, password, enctypes, salt); return_val_if_fail (code == 0, code); memset(&creds, 0, sizeof (creds)); code = krb5_get_init_creds_keytab (k5, &creds, principal, scratch, 0, NULL, NULL); krb5_free_cred_contents (k5, &creds); return code; } krb5_error_code _adcli_krb5_keytab_discover_salt (krb5_context k5, krb5_principal principal, krb5_kvno kvno, krb5_data *password, krb5_enctype *enctypes, krb5_data *salts, int *discovered) { krb5_keytab scratch; krb5_error_code code; int i; /* TODO: This should be a unique name */ code = krb5_kt_resolve (k5, "MEMORY:adcli-discover-salt", &scratch); return_val_if_fail (code == 0, code); for (i = 0; salts[i].data != NULL; i++) { code = _adcli_krb5_keytab_test_salt (k5, scratch, principal, kvno, password, enctypes, &salts[i]); if (code == 0) { *discovered = i; break; } else if (code != KRB5_PREAUTH_FAILED && code != KRB5KDC_ERR_PREAUTH_FAILED) { break; } } krb5_kt_close (k5, scratch); return code; } krb5_error_code _adcli_krb5_w2k3_salt (krb5_context k5, krb5_principal principal, const char *host_netbios, krb5_data *salt) { krb5_data *realm; size_t size = 0; size_t host_length = 0; size_t at = 0; int i; /* * The format for the w2k3 computer account salt is: * REALM | "host" | SAM-Account-Name-Without-$ | "." | realm */ realm = krb5_princ_realm (k5, principal); host_length = strlen (host_netbios); size += realm->length; size += 4; /* "host" */ size += host_length; size += 1; /* "." */ size += realm->length; salt->data = malloc (size); return_val_if_fail (salt->data != NULL, ENOMEM); /* Upper case realm */ for (i = 0; i < realm->length; i++) salt->data[at + i] = toupper (realm->data[i]); at += realm->length; /* The string "host" */ memcpy (salt->data + at, "host", 4); at += 4; /* The netbios name in lower case */ for (i = 0; i < host_length; i++) salt->data[at + i] = tolower (host_netbios[i]); at += host_length; /* The dot */ memcpy (salt->data + at, ".", 1); at += 1; /* Lower case realm */ for (i = 0; i < realm->length; i++) salt->data[at + i] = tolower (realm->data[i]); at += realm->length; assert (at == size); salt->length = size; return 0; } /* for msDs-supportedEncryptionTypes bit defines */ #define MS_KERB_ENCTYPE_DES_CBC_CRC 0x01 #define MS_KERB_ENCTYPE_DES_CBC_MD5 0x02 #define MS_KERB_ENCTYPE_RC4_HMAC_MD5 0x04 #define MS_KERB_ENCTYPE_AES128_CTC_HMAC_SHA1_96 0x08 #define MS_KERB_ENCTYPE_AES256_CTS_HMAC_SHA1_96 0x10 krb5_enctype * _adcli_krb5_parse_enctypes (const char *value) { const int max_enctypes = 5; char *end = NULL; krb5_enctype *enctypes; int types; int at; types = strtoul (value, &end, 10); if (end == NULL || *end != '\0') return NULL; enctypes = calloc (max_enctypes + 1, sizeof (krb5_enctype)); return_val_if_fail (enctypes != NULL, NULL); at = 0; if (types & MS_KERB_ENCTYPE_DES_CBC_CRC) enctypes[at++] = ENCTYPE_DES_CBC_CRC; if (types & MS_KERB_ENCTYPE_DES_CBC_MD5) enctypes[at++] = ENCTYPE_DES_CBC_MD5; if (types & MS_KERB_ENCTYPE_RC4_HMAC_MD5) enctypes[at++] = ENCTYPE_ARCFOUR_HMAC; if (types & MS_KERB_ENCTYPE_AES128_CTC_HMAC_SHA1_96) enctypes[at++] = ENCTYPE_AES128_CTS_HMAC_SHA1_96; if (types & MS_KERB_ENCTYPE_AES256_CTS_HMAC_SHA1_96) enctypes[at++] = ENCTYPE_AES256_CTS_HMAC_SHA1_96; assert (at <= max_enctypes); enctypes[at] = 0; return enctypes; } char * _adcli_krb5_format_enctypes (krb5_enctype *enctypes) { char *value; int types; int i; types = 0; for (i = 0; enctypes[i] != 0; i++) { switch (enctypes[i]) { case ENCTYPE_DES_CBC_CRC: types |= MS_KERB_ENCTYPE_DES_CBC_CRC; break; case ENCTYPE_DES_CBC_MD5: types |= MS_KERB_ENCTYPE_DES_CBC_MD5; break; case ENCTYPE_ARCFOUR_HMAC: types |= MS_KERB_ENCTYPE_RC4_HMAC_MD5; break; case ENCTYPE_AES128_CTS_HMAC_SHA1_96: types |= MS_KERB_ENCTYPE_AES128_CTC_HMAC_SHA1_96; break; case ENCTYPE_AES256_CTS_HMAC_SHA1_96: types |= MS_KERB_ENCTYPE_AES256_CTS_HMAC_SHA1_96; break; default: break; } } if (types == 0) return NULL; if (asprintf (&value, "%d", types) < 0) return_val_if_reached (NULL); return value; }