1 /* $NetBSD: ldap_krb_helper.c,v 1.2 2018/04/07 22:37:30 christos Exp $ */ 2 3 /* ldap_krb_helper.c 4 5 Helper routings for allowing LDAP to read configuration with GSSAPI/krb auth */ 6 7 /* 8 * Copyright (c) 2015-2017 by Internet Systems Consortium, Inc. ("ISC") 9 * Copyright (c) 2014 William B. 10 * All rights reserved. 11 * 12 * Redistribution and use in source and binary forms, with or without 13 * modification, are permitted provided that the following conditions 14 * are met: 15 * 16 * 1. Redistributions of source code must retain the above copyright 17 * notice, this list of conditions and the following disclaimer. 18 * 2. Redistributions in binary form must reproduce the above copyright 19 * notice, this list of conditions and the following disclaimer in the 20 * documentation and/or other materials provided with the distribution. 21 * 3. Neither the name of The Internet Software Consortium nor the names 22 * of its contributors may be used to endorse or promote products derived 23 * from this software without specific prior written permission. 24 * 25 * THIS SOFTWARE IS PROVIDED BY THE INTERNET SOFTWARE CONSORTIUM AND 26 * CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, 27 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 28 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 29 * DISCLAIMED. IN NO EVENT SHALL THE INTERNET SOFTWARE CONSORTIUM OR 30 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 31 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 32 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF 33 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 34 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 35 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT 36 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 37 * SUCH DAMAGE. 38 * 39 * This helper was written by William Brown <william@adelaide.edu.au>, 40 * inspired by krb5_helper.c from bind-dyndb-ldap by Simo Sorce (Redhat) 41 */ 42 #if defined(LDAP_USE_GSSAPI) 43 44 #include "dhcpd.h" 45 #include "ldap_krb_helper.h" 46 47 #include <string.h> 48 #include <stdio.h> 49 #include <unistd.h> 50 #include <time.h> 51 52 #define KRB_DEFAULT_KEYTAB "FILE:/etc/dhcp/dhcp.keytab" 53 #define KRB_MIN_TIME 300 54 55 #define CHECK_KRB5(ctx, err, msg, ...) \ 56 do { \ 57 if (err) { \ 58 const char * errmsg = krb5_get_error_message(ctx, err); \ 59 log_error("Err: %s -> %s\n", msg, errmsg); \ 60 result = ISC_R_FAILURE; \ 61 goto cleanup; \ 62 } \ 63 } while (0) 64 65 #define CHECK(ret_code, msg) \ 66 if (ret_code != 0) { \ 67 log_error("Error, %i %s\n", ret_code, msg); \ 68 goto cleanup; \ 69 } 70 71 static isc_result_t 72 check_credentials(krb5_context context, krb5_ccache ccache, krb5_principal service) 73 { 74 char *realm = NULL; 75 krb5_creds creds; 76 krb5_creds mcreds; 77 krb5_error_code krberr; 78 krb5_timestamp now; 79 isc_result_t result = ISC_R_FAILURE; 80 81 memset(&mcreds, 0, sizeof(mcreds)); 82 memset(&creds, 0, sizeof(creds)); 83 84 krberr = krb5_get_default_realm(context, &realm); 85 CHECK_KRB5(context, krberr, "Failed to retrieve default realm"); 86 87 krberr = krb5_build_principal(context, &mcreds.server, 88 strlen(realm), realm, 89 "krbtgt", realm, NULL); 90 CHECK_KRB5(context, krberr, "Failed to build 'krbtgt/REALM' principal"); 91 92 mcreds.client = service; 93 94 krberr = krb5_cc_retrieve_cred(context, ccache, 0, &mcreds, &creds); 95 96 if (krberr) { 97 const char * errmsg = krb5_get_error_message(context, krberr); 98 log_error("Credentials are not present in cache (%s)\n", errmsg); 99 krb5_free_error_message(context, errmsg); 100 result = ISC_R_FAILURE; 101 goto cleanup; 102 } 103 CHECK_KRB5(context, krberr, "Credentials are not present in cache "); 104 105 krberr = krb5_timeofday(context, &now); 106 CHECK_KRB5(context, krberr, "Failed to get time of day"); 107 108 109 if (now > (creds.times.endtime + KRB_MIN_TIME)) { 110 log_error("Credentials cache expired"); 111 result = ISC_R_FAILURE; 112 goto cleanup; 113 } else { 114 char buf[255]; 115 char fill = ' '; 116 krb5_timestamp_to_sfstring(creds.times.endtime, buf, 16, &fill); 117 log_info("Credentials valid til %s\n", buf); 118 } 119 120 result = ISC_R_SUCCESS; 121 122 cleanup: 123 krb5_free_cred_contents(context, &creds); 124 if (mcreds.server) krb5_free_principal(context, mcreds.server); 125 if (realm) krb5_free_default_realm(context, realm); 126 return result; 127 } 128 129 isc_result_t 130 krb5_get_tgt(const char *principal, const char *keyfile) 131 { 132 isc_result_t result = ISC_R_FAILURE; 133 char *ccname = NULL; 134 krb5_context context = NULL; 135 krb5_error_code krberr; 136 krb5_ccache ccache = NULL; 137 krb5_principal kprincpw = NULL; 138 krb5_creds my_creds; 139 krb5_creds * my_creds_ptr = NULL; 140 krb5_get_init_creds_opt options; 141 krb5_keytab keytab = NULL; 142 int ret; 143 144 if (keyfile == NULL || keyfile[0] == '\0') { 145 keyfile = KRB_DEFAULT_KEYTAB; 146 log_info("Using default keytab %s\n", keyfile); 147 } else { 148 if (strncmp(keyfile, "FILE:", 5) != 0) { 149 log_error("Unknown keytab path format: Does it start with FILE:?\n"); 150 return ISC_R_FAILURE; 151 } 152 } 153 154 krberr = krb5_init_context(&context); 155 CHECK_KRB5(NULL, krberr, "Kerberos context initialization failed"); 156 157 result = ISC_R_SUCCESS; 158 159 ccname = "MEMORY:dhcp_ld_krb5_cc"; 160 log_info("Using ccache %s\n" , ccname); 161 162 ret = setenv("KRB5CCNAME", ccname, 1); 163 if (ret == -1) { 164 log_error("Failed to setup environment\n"); 165 result = ISC_R_FAILURE; 166 goto cleanup; 167 } 168 169 krberr = krb5_cc_resolve(context, ccname, &ccache); 170 CHECK_KRB5(context, krberr, "Couldnt resolve ccache '%s'", ccname); 171 172 krberr = krb5_parse_name(context, principal, &kprincpw); 173 CHECK_KRB5(context, krberr, "Failed to parse princ '%s'", princpal); 174 175 result = check_credentials(context, ccache, kprincpw); 176 if (result == ISC_R_SUCCESS) { 177 log_info("Found valid kerberos credentials\n"); 178 goto cleanup; 179 } else { 180 log_error("No valid krb5 credentials\n"); 181 } 182 183 krberr = krb5_kt_resolve(context, keyfile, &keytab); 184 CHECK_KRB5(context, krberr, 185 "Failed to resolve kt files '%s'\n", keyfile); 186 187 memset(&my_creds, 0, sizeof(my_creds)); 188 memset(&options, 0, sizeof(options)); 189 190 krb5_get_init_creds_opt_set_tkt_life(&options, KRB_MIN_TIME * 2); 191 krb5_get_init_creds_opt_set_address_list(&options, NULL); 192 krb5_get_init_creds_opt_set_forwardable(&options, 0); 193 krb5_get_init_creds_opt_set_proxiable(&options, 0); 194 195 krberr = krb5_get_init_creds_keytab(context, &my_creds, kprincpw, 196 keytab, 0, NULL, &options); 197 CHECK_KRB5(context, krberr, "Failed to get initial credentials TGT\n"); 198 199 my_creds_ptr = &my_creds; 200 201 krberr = krb5_cc_initialize(context, ccache, kprincpw); 202 CHECK_KRB5(context, krberr, "Failed to init ccache\n"); 203 204 krberr = krb5_cc_store_cred(context, ccache, &my_creds); 205 CHECK_KRB5(context, krberr, "Failed to store credentials\n"); 206 207 result = ISC_R_SUCCESS; 208 log_info("Successfully init krb tgt %s", principal); 209 210 cleanup: 211 if (ccache) krb5_cc_close(context, ccache); 212 if (keytab) krb5_kt_close(context, keytab); 213 if (kprincpw) krb5_free_principal(context, kprincpw); 214 if (my_creds_ptr) krb5_free_cred_contents(context, &my_creds); 215 if (context) krb5_free_context(context); 216 return result; 217 } 218 219 #endif /* defined(LDAP_USE_GSSAPI) */ 220