diff options
-rw-r--r-- | source3/Makefile.in | 2 | ||||
-rw-r--r-- | source3/include/safe_string.h | 2 | ||||
-rw-r--r-- | source3/lib/util.c | 15 | ||||
-rw-r--r-- | source3/libads/kerberos_keytab.c | 30 | ||||
-rw-r--r-- | source3/libads/kerberos_verify.c | 297 | ||||
-rw-r--r-- | source3/libads/ldap.c | 353 | ||||
-rw-r--r-- | source3/param/loadparm.c | 3 |
7 files changed, 518 insertions, 184 deletions
diff --git a/source3/Makefile.in b/source3/Makefile.in index c8d2b959a4..56f1ab2487 100644 --- a/source3/Makefile.in +++ b/source3/Makefile.in @@ -228,7 +228,7 @@ KRBCLIENT_OBJ = libads/kerberos.o libads/ads_status.o LIBADS_OBJ = libads/ldap.o libads/ldap_printer.o libads/sasl.o \ libads/krb5_setpw.o libads/ldap_user.o \ - libads/ads_struct.o \ + libads/ads_struct.o libads/kerberos_keytab.o \ libads/disp_sec.o libads/ads_utils.o libads/ldap_utils.o \ libads/ads_ldap.o libads/authdata.o diff --git a/source3/include/safe_string.h b/source3/include/safe_string.h index b22c5efcc9..d278e29aca 100644 --- a/source3/include/safe_string.h +++ b/source3/include/safe_string.h @@ -59,7 +59,7 @@ #ifdef strncasecmp #undef strncasecmp #endif -#define strncasecmp __ERROR__XX__NEVER_USE_STRCASECMP__; +#define strncasecmp __ERROR__XX__NEVER_USE_STRNCASECMP__; #endif /* !_SPLINT_ */ diff --git a/source3/lib/util.c b/source3/lib/util.c index 54cbc36772..554f5ee79d 100644 --- a/source3/lib/util.c +++ b/source3/lib/util.c @@ -2435,6 +2435,21 @@ BOOL unix_wild_match(const char *pattern, const char *string) return unix_do_match(p2, s2) == 0; } +/********************************************************************** + Converts a name to a fully qalified domain name. +***********************************************************************/ + +void name_to_fqdn(fstring fqdn, const char *name) +{ + struct hostent *hp = sys_gethostbyname(name); + if ( hp && hp->h_name && *hp->h_name ) { + DEBUG(10,("name_to_fqdn: lookup for %s -> %s.\n", name, hp->h_name)); + fstrcpy(fqdn,hp->h_name); + } else { + DEBUG(10,("name_to_fqdn: lookup for %s failed.\n", name)); + fstrcpy(fqdn, name); + } +} #ifdef __INSURE__ diff --git a/source3/libads/kerberos_keytab.c b/source3/libads/kerberos_keytab.c index dc25fb74c0..da504db363 100644 --- a/source3/libads/kerberos_keytab.c +++ b/source3/libads/kerberos_keytab.c @@ -30,26 +30,10 @@ #ifdef HAVE_KRB5 /********************************************************************** - Converts a name to a fully qalified domain name. -***********************************************************************/ - -void name_to_fqdn(fstring fqdn, const char *name) -{ - struct hostent *hp = sys_gethostbyname(name); - if ( hp && hp->h_name && *hp->h_name ) { - DEBUG(10,("name_to_fqdn: lookup for %s -> %s.\n", name, hp->h_name)); - fstrcpy(fqdn,hp->h_name); - } else { - DEBUG(10,("name_to_fqdn: lookup for %s failed.\n", name)); - fstrcpy(fqdn, name); - } -} - -/********************************************************************** Adds a single service principal, i.e. 'host' to the system keytab ***********************************************************************/ -int ads_keytab_add_entry(const char *srvPrinc, ADS_STRUCT *ads) +int ads_keytab_add_entry(ADS_STRUCT *ads, const char *srvPrinc) { krb5_error_code ret = 0; krb5_context context = NULL; @@ -254,8 +238,8 @@ int ads_keytab_add_entry(const char *srvPrinc, ADS_STRUCT *ads) /* Update the LDAP with the SPN */ DEBUG(3,("ads_keytab_add_entry: Attempting to add/update '%s'\n", princ_s)); - if (!ADS_ERR_OK(ads_add_spn(ads, global_myname(), srvPrinc))) { - DEBUG(1,("ads_keytab_add_entry: ads_add_spn failed.\n")); + if (!ADS_ERR_OK(ads_add_service_principal_name(ads, global_myname(), srvPrinc))) { + DEBUG(1,("ads_keytab_add_entry: ads_add_service_principcal_name failed.\n")); goto out; } @@ -372,7 +356,7 @@ int ads_keytab_flush(ADS_STRUCT *ads) ZERO_STRUCT(kt_entry); cursor = NULL; - if (!ADS_ERR_OK(ads_clear_spns(ads, global_myname()))) { + if (!ADS_ERR_OK(ads_clear_service_principal_names(ads, global_myname()))) { DEBUG(1,("ads_keytab_flush: Error while clearing service principal listings in LDAP.\n")); goto out; } @@ -413,12 +397,12 @@ int ads_keytab_create_default(ADS_STRUCT *ads) int i, found = 0; char **oldEntries = NULL; - ret = ads_keytab_add_entry("host", ads); + ret = ads_keytab_add_entry(ads, "host"); if (ret) { DEBUG(1,("ads_keytab_create_default: ads_keytab_add_entry failed while adding 'host'.\n")); return ret; } - ret = ads_keytab_add_entry("cifs", ads); + ret = ads_keytab_add_entry(ads, "cifs"); if (ret) { DEBUG(1,("ads_keytab_create_default: ads_keytab_add_entry failed while adding 'cifs'.\n")); return ret; @@ -512,7 +496,7 @@ int ads_keytab_create_default(ADS_STRUCT *ads) ZERO_STRUCT(kt_entry); } for (i = 0; oldEntries[i]; i++) { - ret |= ads_keytab_add_entry(oldEntries[i], ads); + ret |= ads_keytab_add_entry(ads, oldEntries[i]); krb5_free_unparsed_name(context, oldEntries[i]); } krb5_kt_end_seq_get(context, keytab, &cursor); diff --git a/source3/libads/kerberos_verify.c b/source3/libads/kerberos_verify.c index 47559c1abb..2665f40c49 100644 --- a/source3/libads/kerberos_verify.c +++ b/source3/libads/kerberos_verify.c @@ -26,10 +26,166 @@ #ifdef HAVE_KRB5 -/* - verify an incoming ticket and parse out the principal name and - authorization_data if available -*/ +/********************************************************************************** + Try to verify a ticket using the system keytab... the system keytab has kvno -1 entries, so + it's more like what microsoft does... see comment in utils/net_ads.c in the + ads_keytab_add_entry function for details. +***********************************************************************************/ + +static BOOL ads_keytab_verify_ticket(krb5_context context, krb5_auth_context auth_context, + const DATA_BLOB *ticket, krb5_data *p_packet, krb5_ticket **pp_tkt) +{ + krb5_error_code ret = 0; + BOOL auth_ok = False; + + krb5_keytab keytab = NULL; + krb5_kt_cursor cursor = NULL; + krb5_keytab_entry kt_entry; + char *princ_name = NULL; + + ZERO_STRUCT(kt_entry); + ret = krb5_kt_default(context, &keytab); + if (ret) { + DEBUG(1, ("ads_keytab_verify_ticket: krb5_kt_default failed (%s)\n", error_message(ret))); + goto out; + } + + ret = krb5_kt_start_seq_get(context, keytab, &cursor); + if (ret) { + DEBUG(1, ("ads_keytab_verify_ticket: krb5_kt_start_seq_get failed (%s)\n", error_message(ret))); + goto out; + } + + while (!krb5_kt_next_entry(context, keytab, &kt_entry, &cursor)) { + ret = krb5_unparse_name(context, kt_entry.principal, &princ_name); + if (ret) { + DEBUG(1, ("ads_keytab_verify_ticket: krb5_unparse_name failed (%s)\n", error_message(ret))); + goto out; + } + /* Look for a CIFS ticket */ + if (!StrnCaseCmp(princ_name, "cifs/", 5)) { + krb5_auth_con_setuseruserkey(context, auth_context, &kt_entry.key); + + p_packet->length = ticket->length; + p_packet->data = (krb5_pointer)ticket->data; + + if (!(ret = krb5_rd_req(context, &auth_context, p_packet, NULL, NULL, NULL, pp_tkt))) { + krb5_free_unparsed_name(context, princ_name); + princ_name = NULL; + DEBUG(10,("ads_keytab_verify_ticket: enc type [%u] decrypted message !\n", + (unsigned int) kt_entry.key.enctype)); + auth_ok = True; + break; + } + } + krb5_free_unparsed_name(context, princ_name); + princ_name = NULL; + } + if (ret && ret != KRB5_KT_END) { + /* This failed because something went wrong, not because the keytab file was empty. */ + DEBUG(1, ("ads_keytab_verify_ticket: krb5_kt_next_entry failed (%s)\n", error_message(ret))); + goto out; + } + + out: + + if (princ_name) { + krb5_free_unparsed_name(context, princ_name); + } + if (cursor && keytab) { + krb5_kt_end_seq_get(context, keytab, &cursor); + } + if (keytab) { + krb5_kt_close(context, keytab); + } + + return auth_ok; +} + +/********************************************************************************** + Try to verify a ticket using the secrets.tdb. +***********************************************************************************/ + +static BOOL ads_secrets_verify_ticket(krb5_context context, krb5_auth_context auth_context, + krb5_principal host_princ, + const DATA_BLOB *ticket, krb5_data *p_packet, krb5_ticket **pp_tkt) +{ + krb5_error_code ret = 0; + BOOL auth_ok = False; + char *password_s = NULL; + krb5_data password; + krb5_enctype *enctypes = NULL; + int i; + + if (!secrets_init()) { + DEBUG(1,("ads_secrets_verify_ticket: secrets_init failed\n")); + return False; + } + + password_s = secrets_fetch_machine_password(lp_workgroup(), NULL, NULL); + if (!password_s) { + DEBUG(1,("ads_secrets_verify_ticket: failed to fetch machine password\n")); + return False; + } + + password.data = password_s; + password.length = strlen(password_s); + + /* CIFS doesn't use addresses in tickets. This would break NAT. JRA */ + + if ((ret = get_kerberos_allowed_etypes(context, &enctypes))) { + DEBUG(1,("ads_secrets_verify_ticket: krb5_get_permitted_enctypes failed (%s)\n", + error_message(ret))); + goto out; + } + + p_packet->length = ticket->length; + p_packet->data = (krb5_pointer)ticket->data; + + /* We need to setup a auth context with each possible encoding type in turn. */ + for (i=0;enctypes[i];i++) { + krb5_keyblock *key = NULL; + + if (!(key = (krb5_keyblock *)malloc(sizeof(*key)))) { + goto out; + } + + if (create_kerberos_key_from_string(context, host_princ, &password, key, enctypes[i])) { + SAFE_FREE(key); + continue; + } + + krb5_auth_con_setuseruserkey(context, auth_context, key); + + krb5_free_keyblock(context, key); + + if (!(ret = krb5_rd_req(context, &auth_context, p_packet, + NULL, + NULL, NULL, pp_tkt))) { + DEBUG(10,("ads_secrets_verify_ticket: enc type [%u] decrypted message !\n", + (unsigned int)enctypes[i] )); + auth_ok = True; + break; + } + + DEBUG((ret != KRB5_BAD_ENCTYPE) ? 3 : 10, + ("ads_secrets_verify_ticket: enc type [%u] failed to decrypt with error %s\n", + (unsigned int)enctypes[i], error_message(ret))); + } + + out: + + free_kerberos_etypes(context, enctypes); + SAFE_FREE(password_s); + + return auth_ok; +} + +/********************************************************************************** + Verify an incoming ticket and parse out the principal name and + authorization_data if available. +***********************************************************************************/ + NTSTATUS ads_verify_ticket(const char *realm, const DATA_BLOB *ticket, char **principal, DATA_BLOB *auth_data, DATA_BLOB *ap_rep, @@ -41,43 +197,21 @@ NTSTATUS ads_verify_ticket(const char *realm, const DATA_BLOB *ticket, krb5_data packet; krb5_ticket *tkt = NULL; krb5_rcache rcache = NULL; - int ret, i; - krb5_keyblock *key = NULL; + int ret; - krb5_principal host_princ; + krb5_principal host_princ = NULL; char *host_princ_s = NULL; - BOOL free_host_princ = False; BOOL got_replay_mutex = False; fstring myname; - char *password_s = NULL; - krb5_data password; - krb5_enctype *enctypes = NULL; -#if 0 - krb5_address local_addr; - krb5_address remote_addr; -#endif BOOL auth_ok = False; ZERO_STRUCT(packet); - ZERO_STRUCT(password); ZERO_STRUCTP(auth_data); ZERO_STRUCTP(ap_rep); + ZERO_STRUCTP(session_key); - if (!secrets_init()) { - DEBUG(1,("ads_verify_ticket: secrets_init failed\n")); - return NT_STATUS_LOGON_FAILURE; - } - - password_s = secrets_fetch_machine_password(lp_workgroup(), NULL, NULL); - if (!password_s) { - DEBUG(1,("ads_verify_ticket: failed to fetch machine password\n")); - return NT_STATUS_LOGON_FAILURE; - } - - password.data = password_s; - password.length = strlen(password_s); - + initialize_krb5_error_table(); ret = krb5_init_context(&context); if (ret) { DEBUG(1,("ads_verify_ticket: krb5_init_context failed (%s)\n", error_message(ret))); @@ -87,7 +221,6 @@ NTSTATUS ads_verify_ticket(const char *realm, const DATA_BLOB *ticket, ret = krb5_set_default_realm(context, realm); if (ret) { DEBUG(1,("ads_verify_ticket: krb5_set_default_realm failed (%s)\n", error_message(ret))); - sret = NT_STATUS_LOGON_FAILURE; goto out; } @@ -98,22 +231,29 @@ NTSTATUS ads_verify_ticket(const char *realm, const DATA_BLOB *ticket, ret = krb5_auth_con_init(context, &auth_context); if (ret) { DEBUG(1,("ads_verify_ticket: krb5_auth_con_init failed (%s)\n", error_message(ret))); - sret = NT_STATUS_LOGON_FAILURE; goto out; } - fstrcpy(myname, global_myname()); + name_to_fqdn(myname, global_myname()); strlower_m(myname); - asprintf(&host_princ_s, "HOST/%s@%s", myname, lp_realm()); + asprintf(&host_princ_s, "host/%s@%s", myname, lp_realm()); ret = krb5_parse_name(context, host_princ_s, &host_princ); if (ret) { DEBUG(1,("ads_verify_ticket: krb5_parse_name(%s) failed (%s)\n", host_princ_s, error_message(ret))); - sret = NT_STATUS_LOGON_FAILURE; goto out; } - free_host_princ = True; + + /* Lock a mutex surrounding the replay as there is no locking in the MIT krb5 + * code surrounding the replay cache... */ + + if (!grab_server_mutex("replay cache mutex")) { + DEBUG(1,("ads_verify_ticket: unable to protect replay cache with mutex.\n")); + goto out; + } + + got_replay_mutex = True; /* * JRA. We must set the rcache here. This will prevent replay attacks. @@ -122,67 +262,21 @@ NTSTATUS ads_verify_ticket(const char *realm, const DATA_BLOB *ticket, ret = krb5_get_server_rcache(context, krb5_princ_component(context, host_princ, 0), &rcache); if (ret) { DEBUG(1,("ads_verify_ticket: krb5_get_server_rcache failed (%s)\n", error_message(ret))); - sret = NT_STATUS_LOGON_FAILURE; goto out; } ret = krb5_auth_con_setrcache(context, auth_context, rcache); if (ret) { DEBUG(1,("ads_verify_ticket: krb5_auth_con_setrcache failed (%s)\n", error_message(ret))); - sret = NT_STATUS_LOGON_FAILURE; goto out; } - /* CIFS doesn't use addresses in tickets. This would breat NAT. JRA */ - - if ((ret = get_kerberos_allowed_etypes(context, &enctypes))) { - DEBUG(1,("ads_verify_ticket: krb5_get_permitted_enctypes failed (%s)\n", - error_message(ret))); - sret = NT_STATUS_LOGON_FAILURE; - goto out; - } - - /* Lock a mutex surrounding the replay as there is no locking in the MIT krb5 - * code surrounding the replay cache... */ - - if (!grab_server_mutex("replay cache mutex")) { - DEBUG(1,("ads_verify_ticket: unable to protect replay cache with mutex.\n")); - sret = NT_STATUS_LOGON_FAILURE; - goto out; + if (lp_use_kerberos_keytab()) { + auth_ok = ads_keytab_verify_ticket(context, auth_context, ticket, &packet, &tkt); } - - got_replay_mutex = True; - - /* We need to setup a auth context with each possible encoding type in turn. */ - for (i=0;enctypes[i];i++) { - if (!(key = (krb5_keyblock *)malloc(sizeof(*key)))) { - sret = NT_STATUS_NO_MEMORY; - goto out; - } - - if (create_kerberos_key_from_string(context, host_princ, &password, key, enctypes[i])) { - continue; - } - - krb5_auth_con_setuseruserkey(context, auth_context, key); - - krb5_free_keyblock(context, key); - - packet.length = ticket->length; - packet.data = (krb5_pointer)ticket->data; - - if (!(ret = krb5_rd_req(context, &auth_context, &packet, - NULL, - NULL, NULL, &tkt))) { - DEBUG(10,("ads_verify_ticket: enc type [%u] decrypted message !\n", - (unsigned int)enctypes[i] )); - auth_ok = True; - break; - } - - DEBUG((ret != KRB5_BAD_ENCTYPE) ? 3 : 10, - ("ads_verify_ticket: enc type [%u] failed to decrypt with error %s\n", - (unsigned int)enctypes[i], error_message(ret))); + if (!auth_ok) { + auth_ok = ads_secrets_verify_ticket(context, auth_context, host_princ, + ticket, &packet, &tkt); } release_server_mutex(); @@ -191,7 +285,6 @@ NTSTATUS ads_verify_ticket(const char *realm, const DATA_BLOB *ticket, if (!auth_ok) { DEBUG(3,("ads_verify_ticket: krb5_rd_req with auth failed (%s)\n", error_message(ret))); - sret = NT_STATUS_LOGON_FAILURE; goto out; } @@ -199,12 +292,12 @@ NTSTATUS ads_verify_ticket(const char *realm, const DATA_BLOB *ticket, if (ret) { DEBUG(3,("ads_verify_ticket: Failed to generate mutual authentication reply (%s)\n", error_message(ret))); - sret = NT_STATUS_LOGON_FAILURE; goto out; } *ap_rep = data_blob(packet.data, packet.length); - free(packet.data); + SAFE_FREE(packet.data); + packet.length = 0; get_krb5_smb_session_key(context, auth_context, session_key, True); dump_data_pw("SMB session key (from ticket)\n", session_key->data, session_key->length); @@ -241,29 +334,35 @@ NTSTATUS ads_verify_ticket(const char *realm, const DATA_BLOB *ticket, out: - if (got_replay_mutex) + if (got_replay_mutex) { release_server_mutex(); + } - if (!NT_STATUS_IS_OK(sret)) + if (!NT_STATUS_IS_OK(sret)) { data_blob_free(auth_data); + } - if (!NT_STATUS_IS_OK(sret)) + if (!NT_STATUS_IS_OK(sret)) { data_blob_free(ap_rep); + } - if (free_host_princ) + if (host_princ) { krb5_free_principal(context, host_princ); + } - if (tkt != NULL) + if (tkt != NULL) { krb5_free_ticket(context, tkt); - free_kerberos_etypes(context, enctypes); - SAFE_FREE(password_s); + } + SAFE_FREE(host_princ_s); - if (auth_context) + if (auth_context) { krb5_auth_con_free(context, auth_context); + } - if (context) + if (context) { krb5_free_context(context); + } return sret; } diff --git a/source3/libads/ldap.c b/source3/libads/ldap.c index e018eeb2da..3a9c41f09d 100644 --- a/source3/libads/ldap.c +++ b/source3/libads/ldap.c @@ -226,11 +226,10 @@ got_connection: ldap_set_option(ads->ld, LDAP_OPT_PROTOCOL_VERSION, &version); if (!ads->auth.user_name) { - /* by default use the machine account */ - fstring myname; - fstrcpy(myname, global_myname()); - strlower_m(myname); - asprintf(&ads->auth.user_name, "HOST/%s", myname); + fstring my_fqdn; + name_to_fqdn(my_fqdn, global_myname()); + strlower_m(my_fqdn); + asprintf(&ads->auth.user_name, "host/%s", my_fqdn); } if (!ads->auth.realm) { @@ -730,7 +729,7 @@ char *ads_get_dn(ADS_STRUCT *ads, void *msg) * @param host Hostname to search for * @return status of search **/ -ADS_STATUS ads_find_machine_acct(ADS_STRUCT *ads, void **res, const char *host) +ADS_STATUS ads_find_machine_acct(ADS_STRUCT *ads, void **res, const char *machine) { ADS_STATUS status; char *expr; @@ -738,13 +737,13 @@ ADS_STATUS ads_find_machine_acct(ADS_STRUCT *ads, void **res, const char *host) /* the easiest way to find a machine account anywhere in the tree is to look for hostname$ */ - if (asprintf(&expr, "(samAccountName=%s$)", host) == -1) { + if (asprintf(&expr, "(samAccountName=%s$)", machine) == -1) { DEBUG(1, ("asprintf failed!\n")); return ADS_ERROR_NT(NT_STATUS_NO_MEMORY); } status = ads_search(ads, res, expr, attrs); - free(expr); + SAFE_FREE(expr); return status; } @@ -979,18 +978,231 @@ char *ads_ou_string(const char *org_unit) return ads_build_path(org_unit, "\\/", "ou=", 1); } +/** + * Adds (appends) an item to an attribute array, rather then + * replacing the whole list + * @param ctx An initialized TALLOC_CTX + * @param mods An initialized ADS_MODLIST + * @param name name of the ldap attribute to append to + * @param vals an array of values to add + * @return status of addition + **/ + +ADS_STATUS ads_add_strlist(TALLOC_CTX *ctx, ADS_MODLIST *mods, + const char *name, const char **vals) +{ + return ads_modlist_add(ctx, mods, LDAP_MOD_ADD, name, (const void **) vals); +} +/** + * Determines the computer account's current KVNO via an LDAP lookup + * @param ads An initialized ADS_STRUCT + * @param machine_name the NetBIOS name of the computer, which is used to identify the computer account. + * @return the kvno for the computer account, or -1 in case of a failure. + **/ -/* - add a machine account to the ADS server -*/ -static ADS_STATUS ads_add_machine_acct(ADS_STRUCT *ads, const char *hostname, +uint32 ads_get_kvno(ADS_STRUCT *ads, const char *machine_name) +{ + LDAPMessage *res; + uint32 kvno = (uint32)-1; /* -1 indicates a failure */ + char *filter; + const char *attrs[] = {"msDS-KeyVersionNumber", NULL}; + char *dn_string = NULL; + ADS_STATUS ret = ADS_ERROR(LDAP_SUCCESS); + + DEBUG(5,("ads_get_kvno: Searching for host %s\n", machine_name)); + if (asprintf(&filter, "(samAccountName=%s$)", machine_name) == -1) { + return kvno; + } + ret = ads_search(ads, (void**) &res, filter, attrs); + SAFE_FREE(filter); + if (!ADS_ERR_OK(ret) && ads_count_replies(ads, res)) { + DEBUG(1,("ads_get_kvno: Computer Account For %s not found.\n", machine_name)); + return kvno; + } + + dn_string = ads_get_dn(ads, res); + if (!dn_string) { + DEBUG(0,("ads_get_kvno: out of memory.\n")); + return kvno; + } + DEBUG(5,("ads_get_kvno: Using: %s\n", dn_string)); + ads_memfree(ads, dn_string); + + /* --------------------------------------------------------- + * 0 is returned as a default KVNO from this point on... + * This is done because Windows 2000 does not support key + * version numbers. Chances are that a failure in the next + * step is simply due to Windows 2000 being used for a + * domain controller. */ + kvno = 0; + + if (!ads_pull_uint32(ads, res, "msDS-KeyVersionNumber", &kvno)) { + DEBUG(3,("ads_get_kvno: Error Determining KVNO!\n")); + DEBUG(3,("ads_get_kvno: Windows 2000 does not support KVNO's, so this may be normal.\n")); + return kvno; + } + + /* Success */ + DEBUG(5,("ads_get_kvno: Looked Up KVNO of: %d\n", kvno)); + return kvno; +} + +/** + * This clears out all registered spn's for a given hostname + * @param ads An initilaized ADS_STRUCT + * @param machine_name the NetBIOS name of the computer. + * @return 0 upon success, non-zero otherwise. + **/ + +ADS_STATUS ads_clear_service_principal_names(ADS_STRUCT *ads, const char *machine_name) +{ + TALLOC_CTX *ctx; + LDAPMessage *res; + ADS_MODLIST mods; + const char *servicePrincipalName[1] = {NULL}; + ADS_STATUS ret = ADS_ERROR(LDAP_SUCCESS); + char *dn_string = NULL; + + ret = ads_find_machine_acct(ads, (void **)&res, machine_name); + if (!ADS_ERR_OK(ret) || ads_count_replies(ads, res) != 1) { + DEBUG(5,("ads_clear_service_principal_names: WARNING: Host Account for %s not found... skipping operation.\n", machine_name)); + DEBUG(5,("ads_clear_service_principal_names: WARNING: Service Principals for %s have NOT been cleared.\n", machine_name)); + return ADS_ERROR(LDAP_NO_SUCH_OBJECT); + } + + DEBUG(5,("ads_clear_service_principal_names: Host account for %s found\n", machine_name)); + ctx = talloc_init("ads_clear_service_principal_names"); + if (!ctx) { + return ADS_ERROR(LDAP_NO_MEMORY); + } + + if (!(mods = ads_init_mods(ctx))) { + talloc_destroy(ctx); + return ADS_ERROR(LDAP_NO_MEMORY); + } + ret = ads_mod_strlist(ctx, &mods, "servicePrincipalName", servicePrincipalName); + if (!ADS_ERR_OK(ret)) { + DEBUG(1,("ads_clear_service_principal_names: Error creating strlist.\n")); + talloc_destroy(ctx); + return ret; + } + dn_string = ads_get_dn(ads, res); + if (!dn_string) { + talloc_destroy(ctx); + return ADS_ERROR(LDAP_NO_MEMORY); + } + ret = ads_gen_mod(ads, dn_string, mods); + ads_memfree(ads,dn_string); + if (!ADS_ERR_OK(ret)) { + DEBUG(1,("ads_clear_service_principal_names: Error: Updating Service Principals for machine %s in LDAP\n", + machine_name)); + talloc_destroy(ctx); + return ret; + } + + talloc_destroy(ctx); + return ret; +} + +/** + * This adds a service principal name to an existing computer account + * (found by hostname) in AD. + * @param ads An initialized ADS_STRUCT + * @param machine_name the NetBIOS name of the computer, which is used to identify the computer account. + * @param spn A string of the service principal to add, i.e. 'host' + * @return 0 upon sucess, or non-zero if a failure occurs + **/ + +ADS_STATUS ads_add_service_principal_name(ADS_STRUCT *ads, const char *machine_name, const char *spn) +{ + ADS_STATUS ret; + TALLOC_CTX *ctx; + LDAPMessage *res; + char *host_spn, *host_upn, *psp1, *psp2; + ADS_MODLIST mods; + fstring my_fqdn; + char *dn_string = NULL; + const char *servicePrincipalName[3] = {NULL, NULL, NULL}; + + ret = ads_find_machine_acct(ads, (void **)&res, machine_name); + if (!ADS_ERR_OK(ret) || ads_count_replies(ads, res) != 1) { + DEBUG(1,("ads_add_service_principal_name: WARNING: Host Account for %s not found... skipping operation.\n", + machine_name)); + DEBUG(1,("ads_add_service_principal_name: WARNING: Service Principal '%s/%s@%s' has NOT been added.\n", + spn, machine_name, ads->config.realm)); + return ADS_ERROR(LDAP_NO_SUCH_OBJECT); + } + + DEBUG(1,("ads_add_service_principal_name: Host account for %s found\n", machine_name)); + if (!(ctx = talloc_init("ads_add_service_principal_name"))) { + return ADS_ERROR(LDAP_NO_MEMORY); + } + + name_to_fqdn(my_fqdn, machine_name); + if (!(host_spn = talloc_asprintf(ctx, "HOST/%s", my_fqdn))) { + talloc_destroy(ctx); + return ADS_ERROR(LDAP_NO_SUCH_OBJECT); + } + if (!(host_upn = talloc_asprintf(ctx, "%s@%s", host_spn, ads->config.realm))) { + talloc_destroy(ctx); + return ADS_ERROR(LDAP_NO_SUCH_OBJECT); + } + + /* Add the extra principal */ + psp1 = talloc_asprintf(ctx, "%s/%s", spn, machine_name); + strupper_m(psp1); + strlower_m(&psp1[strlen(spn)]); + DEBUG(5,("ads_add_service_principal_name: INFO: Adding %s to host %s\n", psp1, machine_name)); + servicePrincipalName[0] = psp1; + psp2 = talloc_asprintf(ctx, "%s/%s.%s", spn, machine_name, ads->config.realm); + strupper_m(psp2); + strlower_m(&psp2[strlen(spn)]); + DEBUG(5,("ads_add_service_principal_name: INFO: Adding %s to host %s\n", psp2, machine_name)); + servicePrincipalName[1] = psp2; + + if (!(mods = ads_init_mods(ctx))) { + talloc_destroy(ctx); + return ADS_ERROR(LDAP_NO_MEMORY); + } + ret = ads_add_strlist(ctx, &mods, "servicePrincipalName", servicePrincipalName); + if (!ADS_ERR_OK(ret)) { + DEBUG(1,("ads_add_service_principal_name: Error: Updating Service Principals in LDAP\n")); + talloc_destroy(ctx); + return ret; + } + dn_string = ads_get_dn(ads, res); + if (!dn_string) { + talloc_destroy(ctx); + return ADS_ERROR(LDAP_NO_MEMORY); + } + ret = ads_gen_mod(ads, ads_get_dn(ads, res), mods); + ads_memfree(ads,dn_string); + if (!ADS_ERR_OK(ret)) { + DEBUG(1,("ads_add_service_principal_name: Error: Updating Service Principals in LDAP\n")); + talloc_destroy(ctx); + return ret; + } + + talloc_destroy(ctx); + return ret; +} + +/** + * adds a machine account to the ADS server + * @param ads An intialized ADS_STRUCT + * @param machine_name - the NetBIOS machine name of this account. + * @param account_type A number indicating the type of account to create + * @param org_unit The LDAP path in which to place this account + * @return 0 upon success, or non-zero otherwise +**/ + +static ADS_STATUS ads_add_machine_acct(ADS_STRUCT *ads, const char *machine_name, uint32 account_type, const char *org_unit) { ADS_STATUS ret, status; char *host_spn, *host_upn, *new_dn, *samAccountName, *controlstr; - char *ou_str; TALLOC_CTX *ctx; ADS_MODLIST mods; const char *objectClass[] = {"top", "person", "organizationalPerson", @@ -999,87 +1211,106 @@ static ADS_STATUS ads_add_machine_acct(ADS_STRUCT *ads, const char *hostname, char *psp, *psp2; unsigned acct_control; unsigned exists=0; + fstring my_fqdn; LDAPMessage *res; - status = ads_find_machine_acct(ads, (void **)&res, hostname); + if (!(ctx = talloc_init("ads_add_machine_acct"))) + return ADS_ERROR(LDAP_NO_MEMORY); + + ret = ADS_ERROR(LDAP_NO_MEMORY); + + name_to_fqdn(my_fqdn, machine_name); + + status = ads_find_machine_acct(ads, (void **)&res, machine_name); if (ADS_ERR_OK(status) && ads_count_replies(ads, res) == 1) { - DEBUG(0, ("Host account for %s already exists - modifying old account\n", hostname)); + char *dn_string = ads_get_dn(ads, res); + if (!dn_string) { + DEBUG(1, ("ads_add_machine_acct: ads_get_dn returned NULL (malloc failure?)\n")); + goto done; + } + new_dn = talloc_strdup(ctx, dn_string); + ads_memfree(ads,dn_string); + DEBUG(0, ("ads_add_machine_acct: Host account for %s already exists - modifying old account\n", + machine_name)); exists=1; - } + } else { + char *ou_str = ads_ou_string(org_unit); + if (!ou_str) { + DEBUG(1, ("ads_add_machine_acct: ads_ou_string returned NULL (malloc failure?)\n")); + goto done; + } + new_dn = talloc_asprintf(ctx, "cn=%s,%s,%s", machine_name, ou_str, + ads->config.bind_path); - if (!(ctx = talloc_init("machine_account"))) - return ADS_ERROR(LDAP_NO_MEMORY); + SAFE_FREE(ou_str); + } - ret = ADS_ERROR(LDAP_NO_MEMORY); + if (!new_dn) { + goto done; + } - if (!(host_spn = talloc_asprintf(ctx, "HOST/%s", hostname))) + if (!(host_spn = talloc_asprintf(ctx, "HOST/%s", machine_name))) goto done; if (!(host_upn = talloc_asprintf(ctx, "%s@%s", host_spn, ads->config.realm))) goto done; - ou_str = ads_ou_string(org_unit); - if (!ou_str) { - DEBUG(1, ("ads_ou_string returned NULL (malloc failure?)\n")); - goto done; - } - new_dn = talloc_asprintf(ctx, "cn=%s,%s,%s", hostname, ou_str, - ads->config.bind_path); - servicePrincipalName[0] = talloc_asprintf(ctx, "HOST/%s", hostname); + servicePrincipalName[0] = talloc_asprintf(ctx, "HOST/%s", machine_name); psp = talloc_asprintf(ctx, "HOST/%s.%s", - hostname, - ads->config.realm); + machine_name, + ads->config.realm); strlower_m(&psp[5]); servicePrincipalName[1] = psp; - servicePrincipalName[2] = talloc_asprintf(ctx, "CIFS/%s", hostname); + servicePrincipalName[2] = talloc_asprintf(ctx, "CIFS/%s", machine_name); psp2 = talloc_asprintf(ctx, "CIFS/%s.%s", - hostname, + machine_name, ads->config.realm); strlower_m(&psp2[5]); servicePrincipalName[3] = psp2; - free(ou_str); - if (!new_dn) - goto done; - - if (!(samAccountName = talloc_asprintf(ctx, "%s$", hostname))) + if (!(samAccountName = talloc_asprintf(ctx, "%s$", machine_name))) { goto done; + } acct_control = account_type | UF_DONT_EXPIRE_PASSWD; #ifndef ENCTYPE_ARCFOUR_HMAC acct_control |= UF_USE_DES_KEY_ONLY; #endif - if (!(controlstr = talloc_asprintf(ctx, "%u", acct_control))) + if (!(controlstr = talloc_asprintf(ctx, "%u", acct_control))) { goto done; + } - if (!(mods = ads_init_mods(ctx))) + if (!(mods = ads_init_mods(ctx))) { goto done; + } if (!exists) { - ads_mod_str(ctx, &mods, "cn", hostname); + ads_mod_str(ctx, &mods, "cn", machine_name); ads_mod_str(ctx, &mods, "sAMAccountName", samAccountName); ads_mod_str(ctx, &mods, "userAccountControl", controlstr); ads_mod_strlist(ctx, &mods, "objectClass", objectClass); } - ads_mod_str(ctx, &mods, "dNSHostName", hostname); + ads_mod_str(ctx, &mods, "dNSHostName", my_fqdn); ads_mod_str(ctx, &mods, "userPrincipalName", host_upn); ads_mod_strlist(ctx, &mods, "servicePrincipalName", servicePrincipalName); ads_mod_str(ctx, &mods, "operatingSystem", "Samba"); ads_mod_str(ctx, &mods, "operatingSystemVersion", SAMBA_VERSION_STRING); - if (!exists) + if (!exists) { ret = ads_gen_add(ads, new_dn, mods); - else + } else { ret = ads_gen_mod(ads, new_dn, mods); + } - if (!ADS_ERR_OK(ret)) + if (!ADS_ERR_OK(ret)) { goto done; + } /* Do not fail if we can't set security descriptor * it shouldn't be mandatory and probably we just * don't have enough rights to do it. */ if (!exists) { - status = ads_set_machine_sd(ads, hostname, new_dn); + status = ads_set_machine_sd(ads, machine_name, new_dn); if (!ADS_ERR_OK(status)) { DEBUG(0, ("Warning: ads_set_machine_sd: %s\n", @@ -1303,47 +1534,49 @@ int ads_count_replies(ADS_STRUCT *ads, void *res) * Join a machine to a realm * Creates the machine account and sets the machine password * @param ads connection to ads server - * @param hostname name of host to add + * @param machine name of host to add * @param org_unit Organizational unit to place machine in * @return status of join **/ -ADS_STATUS ads_join_realm(ADS_STRUCT *ads, const char *hostname, +ADS_STATUS ads_join_realm(ADS_STRUCT *ads, const char *machine_name, uint32 account_type, const char *org_unit) { ADS_STATUS status; LDAPMessage *res; - char *host; + char *machine; - /* hostname must be lowercase */ - host = strdup(hostname); - strlower_m(host); + /* machine name must be lowercase */ + machine = strdup(machine_name); + strlower_m(machine); /* - status = ads_find_machine_acct(ads, (void **)&res, host); + status = ads_find_machine_acct(ads, (void **)&res, machine); if (ADS_ERR_OK(status) && ads_count_replies(ads, res) == 1) { - DEBUG(0, ("Host account for %s already exists - deleting old account\n", host)); - status = ads_leave_realm(ads, host); + DEBUG(0, ("Host account for %s already exists - deleting old account\n", machine)); + status = ads_leave_realm(ads, machine); if (!ADS_ERR_OK(status)) { DEBUG(0, ("Failed to delete host '%s' from the '%s' realm.\n", - host, ads->config.realm)); + machine, ads->config.realm)); return status; } } */ - status = ads_add_machine_acct(ads, host, account_type, org_unit); + status = ads_add_machine_acct(ads, machine, account_type, org_unit); if (!ADS_ERR_OK(status)) { - DEBUG(0, ("ads_add_machine_acct: %s\n", ads_errstr(status))); + DEBUG(0, ("ads_add_machine_acct (%s): %s\n", machine, ads_errstr(status))); + SAFE_FREE(machine); return status; } - status = ads_find_machine_acct(ads, (void **)&res, host); + status = ads_find_machine_acct(ads, (void **)&res, machine); if (!ADS_ERR_OK(status)) { - DEBUG(0, ("Host account test failed\n")); + DEBUG(0, ("Host account test failed for machine %s\n", machine)); + SAFE_FREE(machine); return status; } - free(host); + SAFE_FREE(machine); return status; } diff --git a/source3/param/loadparm.c b/source3/param/loadparm.c index a163caefc3..247159a7b1 100644 --- a/source3/param/loadparm.c +++ b/source3/param/loadparm.c @@ -287,6 +287,7 @@ typedef struct BOOL bUnixExtensions; BOOL bDisableNetbios; BOOL bKernelChangeNotify; + BOOL bUseKerberosKeytab; int restrict_anonymous; int name_cache_timeout; int client_signing; @@ -861,6 +862,7 @@ static struct parm_struct parm_table[] = { {"hosts deny", P_LIST, P_LOCAL, &sDefault.szHostsdeny, NULL, NULL, FLAG_GLOBAL | FLAG_BASIC | FLAG_ADVANCED | FLAG_SHARE | FLAG_PRINT}, {"deny hosts", P_LIST, P_LOCAL, &sDefault.szHostsdeny, NULL, NULL, FLAG_HIDE}, {"preload modules", P_LIST, P_GLOBAL, &Globals.szPreloadModules, NULL, NULL, FLAG_ADVANCED | FLAG_GLOBAL}, + {"use kerberos keytab", P_BOOL, P_GLOBAL, &Globals.bUseKerberosKeytab, NULL, NULL, FLAG_ADVANCED}, {N_("Logging Options"), P_SEP, P_SEPARATOR}, @@ -1761,6 +1763,7 @@ FN_GLOBAL_BOOL(lp_use_spnego, &Globals.bUseSpnego) FN_GLOBAL_BOOL(lp_client_use_spnego, &Globals.bClientUseSpnego) FN_GLOBAL_BOOL(lp_hostname_lookups, &Globals.bHostnameLookups) FN_GLOBAL_BOOL(lp_kernel_change_notify, &Globals.bKernelChangeNotify) +FN_GLOBAL_BOOL(lp_use_kerberos_keytab, &Globals.bUseKerberosKeytab) FN_GLOBAL_INTEGER(lp_os_level, &Globals.os_level) FN_GLOBAL_INTEGER(lp_max_ttl, &Globals.max_ttl) FN_GLOBAL_INTEGER(lp_max_wins_ttl, &Globals.max_wins_ttl) |