1/* 2 Unix SMB/CIFS implementation. 3 kerberos utility library 4 Copyright (C) Andrew Tridgell 2001 5 Copyright (C) Remus Koos 2001 6 Copyright (C) Luke Howard 2003 7 Copyright (C) Guenther Deschner 2003 8 Copyright (C) Jim McDonough (jmcd@us.ibm.com) 2003 9 10 This program is free software; you can redistribute it and/or modify 11 it under the terms of the GNU General Public License as published by 12 the Free Software Foundation; either version 2 of the License, or 13 (at your option) any later version. 14 15 This program is distributed in the hope that it will be useful, 16 but WITHOUT ANY WARRANTY; without even the implied warranty of 17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 GNU General Public License for more details. 19 20 You should have received a copy of the GNU General Public License 21 along with this program; if not, write to the Free Software 22 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 23*/ 24 25#include "includes.h" 26 27#ifdef HAVE_KRB5 28 29#if !defined(HAVE_KRB5_PRINC_COMPONENT) 30const krb5_data *krb5_princ_component(krb5_context, krb5_principal, int ); 31#endif 32 33/********************************************************************************** 34 Try to verify a ticket using the system keytab... the system keytab has kvno -1 entries, so 35 it's more like what microsoft does... see comment in utils/net_ads.c in the 36 ads_keytab_add_entry function for details. 37***********************************************************************************/ 38 39static BOOL ads_keytab_verify_ticket(krb5_context context, krb5_auth_context auth_context, 40 const DATA_BLOB *ticket, krb5_data *p_packet, krb5_ticket **pp_tkt) 41{ 42 krb5_error_code ret = 0; 43 BOOL auth_ok = False; 44 krb5_keytab keytab = NULL; 45 krb5_kt_cursor kt_cursor; 46 krb5_keytab_entry kt_entry; 47 char *valid_princ_formats[7] = { NULL, NULL, NULL, NULL, NULL, NULL, NULL }; 48 char *entry_princ_s = NULL; 49 fstring my_name, my_fqdn; 50 int i; 51 int number_matched_principals = 0; 52 53 /* Generate the list of principal names which we expect 54 * clients might want to use for authenticating to the file 55 * service. We allow name$,{host,cifs}/{name,fqdn,name.REALM}. */ 56 57 fstrcpy(my_name, global_myname()); 58 59 my_fqdn[0] = '\0'; 60 name_to_fqdn(my_fqdn, global_myname()); 61 62 asprintf(&valid_princ_formats[0], "%s$@%s", my_name, lp_realm()); 63 asprintf(&valid_princ_formats[1], "host/%s@%s", my_name, lp_realm()); 64 asprintf(&valid_princ_formats[2], "host/%s@%s", my_fqdn, lp_realm()); 65 asprintf(&valid_princ_formats[3], "host/%s.%s@%s", my_name, lp_realm(), lp_realm()); 66 asprintf(&valid_princ_formats[4], "cifs/%s@%s", my_name, lp_realm()); 67 asprintf(&valid_princ_formats[5], "cifs/%s@%s", my_fqdn, lp_realm()); 68 asprintf(&valid_princ_formats[6], "cifs/%s.%s@%s", my_name, lp_realm(), lp_realm()); 69 70 ZERO_STRUCT(kt_entry); 71 ZERO_STRUCT(kt_cursor); 72 73 ret = krb5_kt_default(context, &keytab); 74 if (ret) { 75 DEBUG(1, ("ads_keytab_verify_ticket: krb5_kt_default failed (%s)\n", error_message(ret))); 76 goto out; 77 } 78 79 /* Iterate through the keytab. For each key, if the principal 80 * name case-insensitively matches one of the allowed formats, 81 * try verifying the ticket using that principal. */ 82 83 ret = krb5_kt_start_seq_get(context, keytab, &kt_cursor); 84 if (ret) { 85 DEBUG(1, ("ads_keytab_verify_ticket: krb5_kt_start_seq_get failed (%s)\n", error_message(ret))); 86 goto out; 87 } 88 89 ret = krb5_kt_start_seq_get(context, keytab, &kt_cursor); 90 if (ret != KRB5_KT_END && ret != ENOENT ) { 91 while (!auth_ok && (krb5_kt_next_entry(context, keytab, &kt_entry, &kt_cursor) == 0)) { 92 ret = krb5_unparse_name(context, kt_entry.principal, &entry_princ_s); 93 if (ret) { 94 DEBUG(1, ("ads_keytab_verify_ticket: krb5_unparse_name failed (%s)\n", error_message(ret))); 95 goto out; 96 } 97 98 for (i = 0; i < sizeof(valid_princ_formats) / sizeof(valid_princ_formats[0]); i++) { 99 if (strequal(entry_princ_s, valid_princ_formats[i])) { 100 number_matched_principals++; 101 p_packet->length = ticket->length; 102 p_packet->data = (krb5_pointer)ticket->data; 103 *pp_tkt = NULL; 104 ret = krb5_rd_req(context, &auth_context, p_packet, kt_entry.principal, keytab, NULL, pp_tkt); 105 if (ret) { 106 DEBUG(10, ("ads_keytab_verify_ticket: krb5_rd_req(%s) failed: %s\n", 107 entry_princ_s, error_message(ret))); 108 } else { 109 DEBUG(3,("ads_keytab_verify_ticket: krb5_rd_req succeeded for principal %s\n", 110 entry_princ_s)); 111 auth_ok = True; 112 break; 113 } 114 } 115 } 116 117 /* Free the name we parsed. */ 118 krb5_free_unparsed_name(context, entry_princ_s); 119 entry_princ_s = NULL; 120 121 /* Free the entry we just read. */ 122 smb_krb5_kt_free_entry(context, &kt_entry); 123 ZERO_STRUCT(kt_entry); 124 } 125 krb5_kt_end_seq_get(context, keytab, &kt_cursor); 126 } 127 128 ZERO_STRUCT(kt_cursor); 129 130 out: 131 132 if (!auth_ok) { 133 if (!number_matched_principals) { 134 DEBUG(3, ("ads_keytab_verify_ticket: no keytab principals matched expected file service name.\n")); 135 } else { 136 DEBUG(3, ("ads_keytab_verify_ticket: krb5_rd_req failed for all %d matched keytab principals\n", 137 number_matched_principals)); 138 } 139 } 140 141 if (entry_princ_s) { 142 krb5_free_unparsed_name(context, entry_princ_s); 143 } 144 145 { 146 krb5_keytab_entry zero_kt_entry; 147 ZERO_STRUCT(zero_kt_entry); 148 if (memcmp(&zero_kt_entry, &kt_entry, sizeof(krb5_keytab_entry))) { 149 smb_krb5_kt_free_entry(context, &kt_entry); 150 } 151 } 152 153 { 154 krb5_kt_cursor zero_csr; 155 ZERO_STRUCT(zero_csr); 156 if ((memcmp(&kt_cursor, &zero_csr, sizeof(krb5_kt_cursor)) != 0) && keytab) { 157 krb5_kt_end_seq_get(context, keytab, &kt_cursor); 158 } 159 } 160 161 if (keytab) { 162 krb5_kt_close(context, keytab); 163 } 164 return auth_ok; 165} 166 167/********************************************************************************** 168 Try to verify a ticket using the secrets.tdb. 169***********************************************************************************/ 170 171static BOOL ads_secrets_verify_ticket(krb5_context context, krb5_auth_context auth_context, 172 krb5_principal host_princ, 173 const DATA_BLOB *ticket, krb5_data *p_packet, krb5_ticket **pp_tkt) 174{ 175 krb5_error_code ret = 0; 176 BOOL auth_ok = False; 177 char *password_s = NULL; 178 krb5_data password; 179 krb5_enctype *enctypes = NULL; 180 int i; 181 182 if (!secrets_init()) { 183 DEBUG(1,("ads_secrets_verify_ticket: secrets_init failed\n")); 184 return False; 185 } 186 187 password_s = secrets_fetch_machine_password(lp_workgroup(), NULL, NULL); 188 if (!password_s) { 189 DEBUG(1,("ads_secrets_verify_ticket: failed to fetch machine password\n")); 190 return False; 191 } 192 193 password.data = password_s; 194 password.length = strlen(password_s); 195 196 /* CIFS doesn't use addresses in tickets. This would break NAT. JRA */ 197 198 if ((ret = get_kerberos_allowed_etypes(context, &enctypes))) { 199 DEBUG(1,("ads_secrets_verify_ticket: krb5_get_permitted_enctypes failed (%s)\n", 200 error_message(ret))); 201 goto out; 202 } 203 204 p_packet->length = ticket->length; 205 p_packet->data = (krb5_pointer)ticket->data; 206 207 /* We need to setup a auth context with each possible encoding type in turn. */ 208 for (i=0;enctypes[i];i++) { 209 krb5_keyblock *key = NULL; 210 211 if (!(key = SMB_MALLOC_P(krb5_keyblock))) { 212 goto out; 213 } 214 215 if (create_kerberos_key_from_string(context, host_princ, &password, key, enctypes[i])) { 216 SAFE_FREE(key); 217 continue; 218 } 219 220 krb5_auth_con_setuseruserkey(context, auth_context, key); 221 222 krb5_free_keyblock(context, key); 223 224 if (!(ret = krb5_rd_req(context, &auth_context, p_packet, 225 NULL, 226 NULL, NULL, pp_tkt))) { 227 DEBUG(10,("ads_secrets_verify_ticket: enc type [%u] decrypted message !\n", 228 (unsigned int)enctypes[i] )); 229 auth_ok = True; 230 break; 231 } 232 233 DEBUG((ret != KRB5_BAD_ENCTYPE) ? 3 : 10, 234 ("ads_secrets_verify_ticket: enc type [%u] failed to decrypt with error %s\n", 235 (unsigned int)enctypes[i], error_message(ret))); 236 } 237 238 out: 239 240 free_kerberos_etypes(context, enctypes); 241 SAFE_FREE(password_s); 242 243 return auth_ok; 244} 245 246/********************************************************************************** 247 Verify an incoming ticket and parse out the principal name and 248 authorization_data if available. 249***********************************************************************************/ 250 251NTSTATUS ads_verify_ticket(const char *realm, const DATA_BLOB *ticket, 252 char **principal, DATA_BLOB *auth_data, 253 DATA_BLOB *ap_rep, 254 DATA_BLOB *session_key) 255{ 256 NTSTATUS sret = NT_STATUS_LOGON_FAILURE; 257 krb5_context context = NULL; 258 krb5_auth_context auth_context = NULL; 259 krb5_data packet; 260 krb5_ticket *tkt = NULL; 261 krb5_rcache rcache = NULL; 262 int ret; 263 264 krb5_principal host_princ = NULL; 265 char *host_princ_s = NULL; 266 BOOL got_replay_mutex = False; 267 268 BOOL auth_ok = False; 269 270 ZERO_STRUCT(packet); 271 ZERO_STRUCTP(auth_data); 272 ZERO_STRUCTP(ap_rep); 273 ZERO_STRUCTP(session_key); 274 275 initialize_krb5_error_table(); 276 ret = krb5_init_context(&context); 277 if (ret) { 278 DEBUG(1,("ads_verify_ticket: krb5_init_context failed (%s)\n", error_message(ret))); 279 return NT_STATUS_LOGON_FAILURE; 280 } 281 282 ret = krb5_set_default_realm(context, realm); 283 if (ret) { 284 DEBUG(1,("ads_verify_ticket: krb5_set_default_realm failed (%s)\n", error_message(ret))); 285 goto out; 286 } 287 288 /* This whole process is far more complex than I would 289 like. We have to go through all this to allow us to store 290 the secret internally, instead of using /etc/krb5.keytab */ 291 292 ret = krb5_auth_con_init(context, &auth_context); 293 if (ret) { 294 DEBUG(1,("ads_verify_ticket: krb5_auth_con_init failed (%s)\n", error_message(ret))); 295 goto out; 296 } 297 298 asprintf(&host_princ_s, "%s$", global_myname()); 299 strlower_m(host_princ_s); 300 ret = krb5_parse_name(context, host_princ_s, &host_princ); 301 if (ret) { 302 DEBUG(1,("ads_verify_ticket: krb5_parse_name(%s) failed (%s)\n", 303 host_princ_s, error_message(ret))); 304 goto out; 305 } 306 307 308 /* Lock a mutex surrounding the replay as there is no locking in the MIT krb5 309 * code surrounding the replay cache... */ 310 311 if (!grab_server_mutex("replay cache mutex")) { 312 DEBUG(1,("ads_verify_ticket: unable to protect replay cache with mutex.\n")); 313 goto out; 314 } 315 316 got_replay_mutex = True; 317 318 /* 319 * JRA. We must set the rcache here. This will prevent replay attacks. 320 */ 321 322 ret = krb5_get_server_rcache(context, krb5_princ_component(context, host_princ, 0), &rcache); 323 if (ret) { 324 DEBUG(1,("ads_verify_ticket: krb5_get_server_rcache failed (%s)\n", error_message(ret))); 325 goto out; 326 } 327 328 ret = krb5_auth_con_setrcache(context, auth_context, rcache); 329 if (ret) { 330 DEBUG(1,("ads_verify_ticket: krb5_auth_con_setrcache failed (%s)\n", error_message(ret))); 331 goto out; 332 } 333 334 if (lp_use_kerberos_keytab()) { 335 auth_ok = ads_keytab_verify_ticket(context, auth_context, ticket, &packet, &tkt); 336 } 337 if (!auth_ok) { 338 auth_ok = ads_secrets_verify_ticket(context, auth_context, host_princ, 339 ticket, &packet, &tkt); 340 } 341 342 release_server_mutex(); 343 got_replay_mutex = False; 344 345 if (!auth_ok) { 346 DEBUG(3,("ads_verify_ticket: krb5_rd_req with auth failed (%s)\n", 347 error_message(ret))); 348 goto out; 349 } 350 351 ret = krb5_mk_rep(context, auth_context, &packet); 352 if (ret) { 353 DEBUG(3,("ads_verify_ticket: Failed to generate mutual authentication reply (%s)\n", 354 error_message(ret))); 355 goto out; 356 } 357 358 *ap_rep = data_blob(packet.data, packet.length); 359 SAFE_FREE(packet.data); 360 packet.length = 0; 361 362 get_krb5_smb_session_key(context, auth_context, session_key, True); 363 dump_data_pw("SMB session key (from ticket)\n", session_key->data, session_key->length); 364 365#if 0 366 file_save("/tmp/ticket.dat", ticket->data, ticket->length); 367#endif 368 369 get_auth_data_from_tkt(auth_data, tkt); 370 371 { 372 TALLOC_CTX *ctx = talloc_init("pac data"); 373 decode_pac_data(auth_data, ctx); 374 talloc_destroy(ctx); 375 } 376 377#if 0 378 if (tkt->enc_part2) { 379 file_save("/tmp/authdata.dat", 380 tkt->enc_part2->authorization_data[0]->contents, 381 tkt->enc_part2->authorization_data[0]->length); 382 } 383#endif 384 385 if ((ret = krb5_unparse_name(context, get_principal_from_tkt(tkt), 386 principal))) { 387 DEBUG(3,("ads_verify_ticket: krb5_unparse_name failed (%s)\n", 388 error_message(ret))); 389 sret = NT_STATUS_LOGON_FAILURE; 390 goto out; 391 } 392 393 sret = NT_STATUS_OK; 394 395 out: 396 397 if (got_replay_mutex) { 398 release_server_mutex(); 399 } 400 401 if (!NT_STATUS_IS_OK(sret)) { 402 data_blob_free(auth_data); 403 } 404 405 if (!NT_STATUS_IS_OK(sret)) { 406 data_blob_free(ap_rep); 407 } 408 409 if (host_princ) { 410 krb5_free_principal(context, host_princ); 411 } 412 413 if (tkt != NULL) { 414 krb5_free_ticket(context, tkt); 415 } 416 417 SAFE_FREE(host_princ_s); 418 419 if (auth_context) { 420 krb5_auth_con_free(context, auth_context); 421 } 422 423 if (context) { 424 krb5_free_context(context); 425 } 426 427 return sret; 428} 429 430#endif /* HAVE_KRB5 */ 431