[PATCH] keytab management for ADS mode.

Rakesh Patel rapatel at optonline.net
Thu Jan 22 00:54:25 GMT 2004


Attached is a revised version of the patch, which addresses some minor bugs.

Testing for the code was done on Solaris 8 and Solaris 9 joined to a 
"native" Windows2003 domain.
smbclient was tested against both smbd and a W2K3 domain controller with 
file services enabled.
An XP client in a trusted W2K3 domain with user authenticed in the same 
domain as the Solaris boxes was used
to test cross-realm kerberos credentials to the samba server (and for 
PuTTY/GSSAPI-SSPI to openssh as well).

The Solaris machines use openldap+Cyrus SASL + MIT krb5 verion 1.3.1 
along with samba 3.0.2rc1 with
the attached patch.

NOTE: When smbclient communicates with a server, it obtains credentials 
as specified by the server.
Windows2003 SMB/CIFS services utilizes NetBIOS naming conventions and 
supports use of kerberos
principals of the form "machine$@REALM", where machine is the base name 
(not fully qualified) which
is used for NetBIOS naming. When smbclient communicates with smbd, smbd 
will return tickets using
host/machine.domain at REALM, where machine.domain is the fully-qualified 
name returned by DNS.
It is important to note that if you use /etc/hosts first before DNS, the 
fully-qualified names should be
listed first.

Ideally, smbd should probably return cifs/machine.domain at REALM [which is 
what the XP client sees]
to smbclient, however there is no impact in functionality with 
host/machine.domain at REALM as the
service principal.

There were a number of requests for keytab support - hopefully the patch 
can be cleanly integrated after
further testing in various environments.

Rakesh Patel.


RakeshPatel at tdwaterhouse.com wrote:

> The attached patch is based upon the patches posted by Guenther 
> Deschner of SuSE Linux AG.
>
>  
>
> The patch is based upon 3.0.2rc1 and  implements the management of MIT 
> keytab files through the use of "net ads" commands. Testing is 
> required for Heimdal.
>
>  
>
> New configuration options:
>
>  
>
> Keytab file  = /etc/krb5.keytab  ; specify the keytab file to be utilized.
>
>  
>
> Keytab update = yes/no  ; specify whether "net ads changetrustpw" 
> should update the keytab file or not.
>
>                                     If keytab update is set to 'yes',  
> net ads join will automatically update/create the keytab file.
>
>  
>
> As long as the keytab file is specified, "net ads keytab create" will 
> create or update the keytab file using the current key version.
>
>  
>
> The patch utilizes the version number of the host key to ensure the 
> keytab has the correct version. Windows 2000
>
> Did not support key version numbers, so the patch needs to be tested 
> against a Windows 2000 KDC to ensure it does not result in 
> segmentation violations or invalid key version numbers.
>
>  
>
> The intent of the patch is to utilize your Windows 2003 KDC/AD for 
> managing the host keytab and ensure Samba tools/services and other 
> Kerberos services can function simultaneously. The secrets.tdb is 
> utilized to ensure the ability to generate a new random key and update 
> the keytab appropriately.  The advantage of utilizing the Samba 
> facilities include support for RC4-HMAC, support for random passwords 
> as well as the ability to automate and periodically change the host 
> key(s).  This was not possible when using "ktpass" as provided by 
> Microsoft.
>
>  
>
> Please email comments/suggestions/fixes to rapatel at optonline.net 
> <mailto:rapatel at optonline.net> or the samba-technical list.
>
>  
>
> Rakesh Patel.
>
>  
>
>  
>

-------------- next part --------------
--- source/include/ads.h.ORIG	2004-01-16 12:47:52.000000000 -0500
+++ source/include/ads.h	2004-01-20 08:06:29.636599000 -0500
@@ -221,3 +221,12 @@
 #ifndef HAVE_AP_OPTS_USE_SUBKEY
 #define AP_OPTS_USE_SUBKEY 0
 #endif
+
+/* handle different filebased keytab-types
+ * MIT uses "WRFILE:", heimdal uses "FILE:" */
+#ifdef HAVE_WRFILE_KEYTAB
+#define KRB5_KT_FILE_PREFIX "WRFILE"
+#else
+#define KRB5_KT_FILE_PREFIX "FILE"
+#endif
+
--- source/libads/ldap.c.ORIG	2004-01-06 16:08:40.000000000 -0500
+++ source/libads/ldap.c	2004-01-20 10:05:48.993851000 -0500
@@ -226,11 +226,16 @@
 	ldap_set_option(ads->ld, LDAP_OPT_PROTOCOL_VERSION, &version);
 
 	if (!ads->auth.user_name) {
+	        struct hostent *hp;
 		/* by default use the machine account */
 		fstring myname;
 		fstrcpy(myname, global_myname());
+		hp = gethostbyname(myname);
+		if ( hp->h_name && strlen(hp->h_name) > 0 ) {
+		  fstrcpy(myname,hp->h_name);
+		}
 		strlower_m(myname);
-		asprintf(&ads->auth.user_name, "HOST/%s", myname);
+		asprintf(&ads->auth.user_name, "host/%s", myname);
 	}
 
 	if (!ads->auth.realm) {
@@ -1001,6 +1006,9 @@
 	unsigned exists=0;
 	LDAPMessage *res;
 
+	struct hostent *hp;
+	char *fqdn;
+
 	status = ads_find_machine_acct(ads, (void **)&res, hostname);
 	if (ADS_ERR_OK(status) && ads_count_replies(ads, res) == 1) {
 		DEBUG(0, ("Host account for %s already exists - modifying old account\n", hostname));
@@ -1012,7 +1020,14 @@
 
 	ret = ADS_ERROR(LDAP_NO_MEMORY);
 
-	if (!(host_spn = talloc_asprintf(ctx, "HOST/%s", hostname)))
+	hp = gethostbyname(hostname);
+	if ( hp->h_name && strlen(hp->h_name) > 0 ) {
+	  fqdn = strdup(hp->h_name);
+	}
+	else{
+	  fqdn = hostname;
+	}
+	if (!(host_spn = talloc_asprintf(ctx, "HOST/%s", fqdn)))
 		goto done;
 	if (!(host_upn = talloc_asprintf(ctx, "%s@%s", host_spn, ads->config.realm)))
 		goto done;
@@ -1060,7 +1075,7 @@
 		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", fqdn);
 	ads_mod_str(ctx, &mods, "userPrincipalName", host_upn);
 	ads_mod_strlist(ctx, &mods, "servicePrincipalName", servicePrincipalName);
 	ads_mod_str(ctx, &mods, "operatingSystem", "Samba");
--- source/libads/kerberos_verify.c.ORIG	2004-01-16 12:47:52.000000000 -0500
+++ source/libads/kerberos_verify.c	2004-01-21 15:20:42.789618000 -0500
@@ -38,8 +38,8 @@
 	}
 }
 
-#ifdef HAVE_MEMORY_KEYTAB
-static krb5_error_code create_keytab(krb5_context context,
+
+krb5_error_code create_keytab(krb5_context context,
 				     krb5_principal host_princ,
 				     char *host_princ_s,
 				     krb5_data password,
@@ -53,6 +53,7 @@
 	krb5_keyblock *key;
 	int i;
 
+	kvno = get_kvno(host_princ_s,password.data);
 	DEBUG(10,("creating keytab: %s\n", keytab_name));
 	ret = krb5_kt_resolve(context, keytab_name, keytab);
 	if (ret) 
@@ -61,7 +62,7 @@
 	if (!(key = (krb5_keyblock *)malloc(sizeof(*key)))) {
 		return ENOMEM;
 	}
-	
+
 	/* add keytab entries for all encryption types */
 	for ( i=0; enctypes[i]; i++ ) {
 		
@@ -84,8 +85,11 @@
 		entry.keyblock = *key;
 #endif
 
-		DEBUG(10,("adding keytab-entry for (%s) with encryption type (%d)\n",
-				host_princ_s, enctypes[i]));
+#ifdef HAVE_KV5M_KEYTAB
+		entry.magic = KV5M_KEYTAB;
+#endif
+		DEBUG(10,("adding keytab-entry for (%s) with encryption type (%d) and version (%d)\n",
+				host_princ_s, enctypes[i], entry.vno));
 		ret = krb5_kt_add_entry(context, *keytab, &entry);
 		if (ret) {
 			DEBUG(1,("adding entry to keytab failed (%s)\n", 
@@ -98,7 +102,7 @@
 	
 	return 0;
 }
-#endif
+
 
 static BOOL setup_keytab(krb5_context context,
 			 krb5_principal host_princ,
@@ -110,17 +114,11 @@
 	char *keytab_name = NULL;
 	krb5_error_code ret;
 
-	/* check if we have to setup a keytab - not currently enabled
-	   I've put this in so that the else block below functions 
-	   the same way that it will when this code is turned on */
-	if (0 /* will later be *lp_keytab() */) {
+	/* check if we have to setup a keytab */
+	if (*lp_keytab_file()) {
 
 		/* use a file-keytab */
-		asprintf(&keytab_name, "%s:%s", 
-			 "" 
-			 /* KRB5_KT_FILE_PREFIX, "FILE" or 
-			    "WRFILE" depending on HEeimdal or MIT */, 
-			 "" /* will later be lp_keytab() */);
+		asprintf(&keytab_name, "%s:%s", KRB5_KT_FILE_PREFIX, lp_keytab_file());
 
 	        DEBUG(10,("will use filebased keytab: %s\n", keytab_name));
 	        ret = krb5_kt_resolve(context, keytab_name, keytab);
@@ -193,6 +191,8 @@
 	ZERO_STRUCTP(auth_data);
 	ZERO_STRUCTP(ap_rep);
 
+	struct hostent *hp;
+
 	if (!secrets_init()) {
 		DEBUG(1,("ads_verify_ticket: secrets_init failed\n"));
 		return NT_STATUS_LOGON_FAILURE;
@@ -200,7 +200,7 @@
 
 	password_s = secrets_fetch_machine_password(lp_workgroup(), NULL, NULL);
 	if (!password_s) {
-		DEBUG(1,("ads_verify_ticket: failed to fetch machine password\n"));
+		DEBUG(1,("ads_verify_ticket: failed to fetch machine password for domain %s\n",lp_workgroup()));
 		return NT_STATUS_LOGON_FAILURE;
 	}
 
@@ -232,8 +232,13 @@
 	}
 
 	fstrcpy(myname, global_myname());
+	/* The hostname in the machine principal must be FQDN as per RFC. */
+	hp = gethostbyname(myname);
+	if ( hp->h_name && strlen(hp->h_name) > 0 ) {
+	  fstrcpy(myname,hp->h_name);
+	}
 	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",
@@ -306,11 +311,7 @@
 		packet.data = (krb5_pointer)ticket->data;
 
 		if (!(ret = krb5_rd_req(context, &auth_context, &packet, 
-#ifdef HAVE_MEMORY_KEYTAB
-					host_princ, 
-#else
-					NULL,
-#endif
+					NULL, 
 					keytab, NULL, &tkt))) {
 			DEBUG(10,("ads_verify_ticket: enc type [%u] decrypted message !\n",
 				(unsigned int)enctypes[i] ));
@@ -406,4 +407,166 @@
 	return sret;
 }
 
+krb5_kvno get_kvno(char *hprinc_s, char *pwd)
+{
+    krb5_context context;
+    krb5_error_code ret;
+    krb5_enctype etype;
+    krb5_ccache ccache;
+    krb5_creds in_creds, *out_creds;
+    krb5_ticket *ticket;
+
+    krb5_creds my_creds;
+    krb5_error_code code = 0;
+    krb5_get_init_creds_opt options;
+    krb5_principal hprinc;
+
+    char *myprinc_s = NULL;
+
+    krb5_kvno kvno;
+    
+      fstring myname;
+      struct hostent *hp;
+
+	fstrcpy(myname, global_myname());
+	/* The hostname in the machine principal must be FQDN as per RFC. */
+	hp = gethostbyname(myname);
+	if ( hp->h_name && strlen(hp->h_name) > 0 ) {
+	  fstrcpy(myname,hp->h_name);
+	}
+	strlower_m(myname);
+	asprintf(&myprinc_s, "host/%s@%s", myname, lp_realm());
+
+    ret = krb5_init_context(&context);
+    if (ret) {
+	DEBUG(1,("while initializing krb5 library (%d)",ret));
+	SAFE_FREE(myprinc_s);
+	return(-1);
+    }
+
+    etype = 0;
+
+    ret = krb5_parse_name(context, myprinc_s, &hprinc);
+
+    if (ret) {
+
+      printf("while parsing name %s failed (%s)\n",
+
+	     myprinc_s, error_message(ret));
+
+	SAFE_FREE(myprinc_s);
+      return(-1);
+
+    }
+
+    ret = krb5_cc_default(context, &ccache);
+    if (ret) {
+	DEBUG(1,("while opening ccache (%d)",ret));
+	krb5_free_context(context);
+	SAFE_FREE(myprinc_s);
+	return(-1);
+    }
+
+    code = krb5_cc_initialize(context, ccache, hprinc);
+    if (code) {
+	DEBUG(1,("when initializing ccache (%d)",ret));
+      krb5_free_context(context);
+	SAFE_FREE(myprinc_s);
+      return(-1);
+    }
+                                                                                                                                                                                                                                             
+    krb5_get_init_creds_opt_init(&options);
+    memset(&my_creds, 0, sizeof(my_creds));
+ 
+    if ((code = krb5_copy_principal(context, hprinc, &my_creds.client))){
+	DEBUG(1,("while copying principal (%d)",ret));
+	    krb5_cc_close(context, ccache);
+	    krb5_free_context(context);
+	SAFE_FREE(myprinc_s);
+      return(-1);
+    }
+                                                                                                                                                                                                                                             
+    if ((code = krb5_copy_principal(context, hprinc, &my_creds.server))){
+	DEBUG(1,("while copying principal (%d)",ret));
+	    krb5_cc_close(context, ccache);
+	    krb5_free_context(context);
+	SAFE_FREE(myprinc_s);
+      return(-1);
+    }
+                                                                                                                                                                                                                                             
+    my_creds.times.starttime = 0;       /* start timer when request
+                                           gets to KDC */
+                                                                                                                                                                                                                                             
+    my_creds.times.renew_till = 0;
+
+    ret = krb5_get_init_creds_password(context,&my_creds, hprinc, pwd, NULL, 0, 0, myprinc_s, &options);
+
+    if (ret) {
+	DEBUG(1,("whule getting client creds (%d)",ret));
+	    krb5_cc_close(context, ccache);
+	    krb5_free_context(context);
+	SAFE_FREE(myprinc_s);
+	return(-1);
+    }
+
+    code = krb5_cc_store_cred(context, ccache, &my_creds);
+    if (code) {
+	DEBUG(1,("while storing credentials (%d)",ret));
+	    krb5_cc_close(context, ccache);
+	    krb5_free_context(context);
+	SAFE_FREE(myprinc_s);
+	return(-1);
+    }
+                                                                                                                                                                                                                                             
+
+	memset(&in_creds, 0, sizeof(in_creds));
+
+	if ((code = krb5_copy_principal(context, hprinc, &in_creds.server))){
+	  DEBUG(1,("while copying principal (%d)",ret));
+	  krb5_cc_close(context, ccache);
+	  krb5_free_context(context);
+	SAFE_FREE(myprinc_s);
+	  return(-1);
+	}
+
+	if ((code = krb5_copy_principal(context, hprinc, &in_creds.client))){
+	  DEBUG(1,("while copying principal (%d)",ret));
+	  krb5_cc_close(context, ccache);
+	  krb5_free_context(context);
+	SAFE_FREE(myprinc_s);
+	  return(-1);
+	}
+
+	/*	in_creds.client = hprinc; */
+
+	in_creds.keyblock.enctype = etype;
+
+	/* in_creds.server = hprinc; */
+
+	ret = krb5_get_credentials(context, 0, ccache, &in_creds, &out_creds);
+
+	/* we need a native ticket */
+	ret = krb5_decode_ticket(&out_creds->ticket, &ticket);
+	if (ret) {
+	  DEBUG(1,("whule decoding ticket (%d), myprinc_s: %s, error message: %s",ret,myprinc_s,error_message(ret)));
+
+	    krb5_free_creds(context, out_creds);
+	    krb5_cc_close(context, ccache);
+	    krb5_free_context(context);
+	    SAFE_FREE(myprinc_s);
+	    return(-1);
+	}
+	else {
+	  kvno = ticket->enc_part.kvno;
+	  DEBUG(1,("myprinc_s: %s,kvno: %d",myprinc_s,kvno));
+	  krb5_free_ticket(context, ticket);
+	  krb5_free_creds(context, out_creds);
+	}
+
+    krb5_cc_close(context, ccache);
+    krb5_free_context(context);
+    SAFE_FREE(myprinc_s);
+    return(kvno);
+
+}
 #endif /* HAVE_KRB5 */
--- source/param/loadparm.c.ORIG	2004-01-16 12:47:53.000000000 -0500
+++ source/param/loadparm.c	2004-01-20 08:06:29.696597000 -0500
@@ -119,6 +119,8 @@
 	char *szPasswdChat;
 	char *szLogFile;
 	char *szConfigFile;
+	char *szKeytabFile;
+	BOOL bKeytabUpdate;
 	char *szSMBPasswdFile;
 	char *szPrivateDir;
 	char **szPassdbBackend;
@@ -1086,6 +1088,8 @@
 	{"delete share command", P_STRING, P_GLOBAL, &Globals.szDeleteShareCommand, NULL, NULL, FLAG_ADVANCED}, 
 
 	{"config file", P_STRING, P_GLOBAL, &Globals.szConfigFile, NULL, NULL, FLAG_HIDE}, 
+	{"keytab file", P_STRING, P_GLOBAL, &Globals.szKeytabFile, NULL, NULL, FLAG_HIDE}, 
+	{"keytab update", P_BOOL, P_GLOBAL, &Globals.bKeytabUpdate, NULL, NULL, FLAG_HIDE}, 
 	{"preload", P_STRING, P_GLOBAL, &Globals.szAutoServices, NULL, NULL, FLAG_ADVANCED}, 
 	{"auto services", P_STRING, P_GLOBAL, &Globals.szAutoServices, NULL, NULL, FLAG_ADVANCED}, 
 	{"lock directory", P_STRING, P_GLOBAL, &Globals.szLockDir, NULL, NULL, FLAG_ADVANCED}, 
@@ -1526,6 +1530,8 @@
 	Globals.server_signing = False;
 
 	string_set(&Globals.smb_ports, SMB_PORTS);
+	
+	Globals.bKeytabUpdate = False;
 }
 
 static TALLOC_CTX *lp_talloc;
@@ -1614,6 +1620,8 @@
 FN_GLOBAL_STRING(lp_display_charset, &Globals.display_charset)
 FN_GLOBAL_STRING(lp_logfile, &Globals.szLogFile)
 FN_GLOBAL_STRING(lp_configfile, &Globals.szConfigFile)
+FN_GLOBAL_STRING(lp_keytab_file, &Globals.szKeytabFile)
+FN_GLOBAL_BOOL(lp_keytab_update, &Globals.bKeytabUpdate)
 FN_GLOBAL_STRING(lp_smb_passwd_file, &Globals.szSMBPasswdFile)
 FN_GLOBAL_STRING(lp_private_dir, &Globals.szPrivateDir)
 FN_GLOBAL_STRING(lp_serverstring, &Globals.szServerString)
--- source/smbd/negprot.c.ORIG	2003-08-15 16:39:59.000000000 -0400
+++ source/smbd/negprot.c	2004-01-21 15:15:03.797996000 -0500
@@ -209,9 +209,11 @@
 	if (lp_security() != SEC_ADS) {
 		blob = spnego_gen_negTokenInit(guid, OIDs_plain, "NONE");
 	} else {
-		asprintf(&principal, "%s$@%s", guid, lp_realm());
-		blob = spnego_gen_negTokenInit(guid, OIDs_krb5, principal);
-		free(principal);
+	  struct hostent *hp;
+	  hp = gethostbyname(guid);
+	  asprintf(&principal, "host/%s@%s", hp->h_name, lp_realm());
+	  blob = spnego_gen_negTokenInit(guid, OIDs_krb5, principal);
+	  free(principal);
 	}
 	memcpy(p, blob.data, blob.length);
 	len = blob.length;
--- source/utils/net.c.ORIG	2003-12-04 16:38:40.000000000 -0500
+++ source/utils/net.c	2004-01-20 08:06:29.736594000 -0500
@@ -73,6 +73,8 @@
 int opt_timeout = 0;
 const char *opt_target_workgroup = NULL;
 int opt_machine_pass = 0;
+const char *opt_keytab_file = "";
+int opt_keytab_update = 0;
 
 BOOL opt_have_ip = False;
 struct in_addr opt_dest_ip;
@@ -656,6 +658,8 @@
 		{"timeout",	't', POPT_ARG_INT,    &opt_timeout},
 		{"machine-pass",'P', POPT_ARG_NONE,   &opt_machine_pass},
 		{"myworkgroup", 'W', POPT_ARG_STRING, &opt_workgroup},
+		{"keytabfile",      'k', POPT_ARG_STRING, &opt_keytab_file},
+		{"keytabupdate",'K', POPT_ARG_NONE,   &opt_keytab_update},
 		POPT_COMMON_SAMBA
 		{ 0, 0, 0, 0}
 	};
--- source/utils/net_ads.c.ORIG	2004-01-16 12:47:53.000000000 -0500
+++ source/utils/net_ads.c	2004-01-21 15:30:54.792547000 -0500
@@ -55,6 +55,8 @@
 "\n\tperform a raw LDAP search and dump the results\n"
 "\nnet ads dn"\
 "\n\tperform a raw LDAP search and dump attributes of a particular DN\n"
+"\nnet ads keytab"\
+"\n\tdisplays or creates a kerberos keytab-file\n"
 		);
 	return -1;
 }
@@ -775,6 +777,15 @@
 	SAFE_FREE(machine_account);
 	if ( ctx )
 		talloc_destroy(ctx);
+
+	if ( opt_keytab_update == 1 || lp_keytab_update() == 1 ) {
+	  int retval;
+	  retval = (int) ads_keytab_create();
+	  if ( retval ) {
+	    d_printf("Error (%d) creating host keytab!!",retval);
+	  }
+	}
+
 	return 0;
 }
 
@@ -1077,6 +1088,8 @@
     char *host_principal;
     char *hostname;
     ADS_STATUS ret;
+    
+    struct hostent *hp;
 
     if (!secrets_init()) {
 	    DEBUG(1,("Failed to initialise secrets database\n"));
@@ -1091,7 +1104,13 @@
 	    return -1;
     }
 
-    hostname = strdup(global_myname());
+    hp = gethostbyname(global_myname());
+    if ( hp->h_name && strlen(hp->h_name) > 0 ) {
+      hostname = strdup(hp->h_name);
+    }
+    else {
+      hostname = strdup(global_myname());
+    }
     strlower_m(hostname);
     asprintf(&host_principal, "%s@%s", hostname, ads->config.realm);
     SAFE_FREE(hostname);
@@ -1110,6 +1129,14 @@
     ads_destroy(&ads);
     SAFE_FREE(host_principal);
 
+    if ( opt_keytab_update == 1  || lp_keytab_update() == 1 ) {
+      int retval;
+	retval = (int) ads_keytab_create();
+	if ( retval ) {
+	  d_printf("Error (%d) creating/updating host keytab!",retval); 
+	}
+    }
+
     return 0;
 }
 
@@ -1231,6 +1258,261 @@
 }
 
 
+int ads_keytab_create()
+{
+	krb5_error_code ret;
+	krb5_context context;
+	krb5_keytab keytab;
+	krb5_kt_cursor cursor;
+	krb5_keytab_entry entry;
+	krb5_principal host_princ;
+	krb5_principal cifs_princ;
+	krb5_data password;
+	krb5_enctype *enctypes = NULL;
+	
+	char *principal = NULL;
+	char *keytab_name = NULL;
+	char *host_princ_s = NULL;
+	char *cifs_princ_s = NULL;
+	char *password_s = NULL;
+	fstring myname;
+
+	struct hostent *hp;
+
+	/* resolve the right keytab */
+	if (*opt_keytab_file) {
+		asprintf(&keytab_name, "%s:%s", KRB5_KT_FILE_PREFIX, opt_keytab_file);
+	} else if (*lp_keytab_file()) {
+		asprintf(&keytab_name, "%s:%s", KRB5_KT_FILE_PREFIX, lp_keytab_file());
+	} else {
+		printf("no known keytab\n");
+		return -1;
+	}
+	printf("creating keytab: %s\n",keytab_name);
+
+	initialize_krb5_error_table();
+	ret = krb5_init_context(&context);
+	if (ret) {
+		DEBUG(1,("could not krb5_init_context: %s\n",error_message(ret)));
+		return -1;
+	}
+	ret = krb5_kt_resolve(context,keytab_name,&keytab);
+	if (ret) {
+		DEBUG(1,("krb5_kt_resolve failed (%s)\n",error_message(ret)));
+		return -1;
+	}
+
+	ret = get_kerberos_allowed_etypes(context,&enctypes);
+	if (ret) {
+		DEBUG(1,("get_kerberos_allowed_etypes failed (%s)\n",error_message(ret)));
+		return -1;
+	}
+
+	/* retrieve the password */
+	if (!secrets_init()) {
+		DEBUG(1,("secrets_init failed\n"));
+		return -1;
+	}
+	password_s = secrets_fetch_machine_password(lp_workgroup(), NULL, NULL);
+	if (!password_s) {
+		DEBUG(1,("failed to fetch machine password\n"));
+		return -1;
+	}
+	password.data = password_s;
+	password.length = strlen(password_s);
+
+	/* construct our principal */
+
+	fstrcpy(myname, global_myname());
+	hp = gethostbyname(myname);
+	if ( hp->h_name && strlen(hp->h_name) > 0 ) {
+	  fstrcpy(myname,hp->h_name);
+	}
+	strlower_m(myname);
+	asprintf(&host_princ_s, "host/%s@%s", myname, lp_realm());
+	asprintf(&cifs_princ_s, "cifs/%s@%s", myname, lp_realm());
+	ret = krb5_parse_name(context, host_princ_s, &host_princ);
+	if (ret) {
+		DEBUG(1,("krb5_parse_name(%s) failed (%s)\n",host_princ_s, error_message(ret)));
+		return -1;
+	}
+
+	/* seek and delete old keytab entries */
+	ret = krb5_kt_start_seq_get(context, keytab, &cursor);
+	if (ret != KRB5_KT_END && ret != ENOENT ) {
+		printf("will try to delete old entries\n");
+		while((ret = krb5_kt_next_entry(context, keytab, &entry, &cursor)) == 0){
+		  int retval = 0;
+		  char *ktprinc = NULL;
+		  krb5_unparse_name(context, entry.principal, &ktprinc);
+#ifdef HAVE_KRB5_KT_COMPARE
+		  if ( krb5_kt_compare(context, &entry, host_princ, 0, 0) == True )
+#else
+	          if ( strcmp(ktprinc,host_princ_s) == 0 || strcmp(ktprinc,cifs_princ_s) == 0 )
+#endif
+		  {
+		    if ( strcmp(ktprinc,host_princ_s) == 0) {
+			    printf("found old entry for principal: %s (deleting)\n", host_princ_s);
+		    }
+		    if ( strcmp(ktprinc,cifs_princ_s) == 0 ) {
+			    printf("found old entry for principal: %s (deleting)\n", cifs_princ_s);
+		    }
+
+			    /* MIT kludge - can't remove keytab entry while scanning entries... */ 
+			    ret = krb5_kt_end_seq_get(context, keytab, &cursor);
+			    if (ret) {
+					DEBUG(1,("krb5_kt_end_seq_get() failed (%s)\n",error_message(ret)));
+					return -1;
+			    }
+			    ret = krb5_kt_remove_entry(context, keytab, &entry);
+			    if (ret) {
+					DEBUG(1,("krb5_kt_remove_entry failed (%s)\n",error_message(ret)));
+					return -1;
+			    }
+			    ret = krb5_kt_start_seq_get(context, keytab, &cursor);
+			    if (ret) {
+					DEBUG(1,("krb5_kt_start_seq failed (%s)\n",error_message(ret)));
+					return -1;
+			    }
+			    ret = krb5_kt_free_entry(context, &entry);
+			    if (ret) {
+					DEBUG(1,("krb5_kt_remove_entry failed (%s)\n",error_message(ret)));
+					return -1;
+			    }
+			    free(ktprinc);
+			    continue;
+		  }
+		  ret = krb5_kt_free_entry(context, &entry);
+		  if (ret) 
+		    DEBUG(1,("krb5_kt_free_entry failed (%s)\n",error_message(ret)));
+		  free(ktprinc); 
+	        }
+		ret = krb5_kt_end_seq_get(context, keytab, &cursor);
+		if (ret)
+			DEBUG(1,("krb5_kt_end_seq_get failed (%s)\n",error_message(ret)));
+	}
+
+	/* store new entries in keytab */
+	ret = create_keytab(context, host_princ, host_princ_s, password, enctypes, &keytab, keytab_name);
+	if (ret) {
+		DEBUG(1,("could not create keytab: %s\n",error_message(ret)));
+		return -1;
+	}
+	ret = krb5_parse_name(context, cifs_princ_s, &cifs_princ);
+	if (ret) {
+		DEBUG(1,("krb5_parse_name failed (%s)\n",error_message(ret)));
+	}
+	ret = create_keytab(context, cifs_princ, cifs_princ_s, password, enctypes, &keytab, keytab_name);
+	if (ret) {
+		DEBUG(1,("could not update keytab: %s\n",error_message(ret)));
+		return -1;
+	}
+
+	return 0;
+		
+}
+
+static int net_ads_keytab_usage(int argc, const char **argv)
+{
+	d_printf(
+"\nnet ads keytab"
+"\n\tlist the contents of the keytab"
+"\nnet ads keytab create"
+"\n\twill create a new keytab"
+"\n\nthe default location of your keytab (defined in smb.conf)"
+"\ncan be overridden with --keytab=/my/keytab\n\n");
+	return -1;
+}
+
+int net_ads_keytab(int argc, const char **argv)
+{
+	struct functable func[] = {
+		{"CREATE", ads_keytab_create},
+		{"HELP", net_ads_keytab_usage},
+		{NULL, NULL}
+	};
+
+	if (argc == 0) {
+
+		/* list the keytab */
+		krb5_error_code ret;
+		krb5_context context;
+		krb5_keytab keytab;
+		krb5_kt_cursor cursor;
+		krb5_keytab_entry entry;
+		char enctype_s[256];
+		char *principal = NULL;
+		char *keytab_name = NULL;
+
+		/* resolve the correct keytab */
+		if (*opt_keytab_file) {
+			asprintf(&keytab_name, "%s:%s", KRB5_KT_FILE_PREFIX, opt_keytab_file);
+		} else if (*lp_keytab_file()) {
+			asprintf(&keytab_name, "%s:%s", KRB5_KT_FILE_PREFIX, lp_keytab_file());
+		} else {
+			printf("no known keytab\n");
+			free(keytab_name);
+			return -1;
+		} 
+		printf("\nlisting keytab: %s\n",keytab_name);
+
+		initialize_krb5_error_table();
+		ret = krb5_init_context(&context);
+		if (ret) {
+			DEBUG(1,("could not krb5_init_context: %s\n",error_message(ret)));
+			return -1;
+		}
+		ret = krb5_kt_resolve(context,keytab_name,&keytab);
+		if (ret) {
+			DEBUG(1,("could not krb5_kt_resolve: %s\n",error_message(ret)));
+			return -1;
+		}
+
+		/* iterate through all entries and print some data */
+		ret = krb5_kt_start_seq_get(context, keytab, &cursor);
+		if (ret) {
+			DEBUG(1,("could not krb5_kt_start_seq_get: %s\n",error_message(ret)));
+			return -1;
+		}
+
+		printf("kvno, principal, timestamp, enctype \n");
+		printf("-----------------------------------\n");
+		while ((ret = krb5_kt_next_entry(context, keytab, &entry, &cursor)) == 0) {
+			krb5_unparse_name(context, entry.principal, &principal);
+#ifdef HAVE_KRB5_KEYBLOCK_KEYVALUE
+			ret = krb5_enctype_to_string(context, entry.keyblock.keytype, &enctype_s);
+#else 
+			memset(enctype_s,0x00,256);
+			ret = krb5_enctype_to_string(entry.key.enctype, enctype_s, 256);
+#endif
+			if (ret)
+				DEBUG(1,("could not krb5_enctype_to_string: %s\n",error_message(ret)));
+			printf("%d, %s, (%s), %s\n", 
+					entry.vno, principal, http_timestring(entry.timestamp), enctype_s);
+			free(principal);
+			krb5_kt_free_entry(context, &entry);
+		}
+
+		ret = krb5_kt_end_seq_get(context, keytab, &cursor);
+		if (ret) {
+			DEBUG(1,("could not krb5_kt_end_seq_get: %s\n",error_message(ret)));
+			return -1;
+		}
+
+		free(keytab_name);
+		free(enctype_s);
+	
+		ret = krb5_kt_close(context, keytab);
+		if (ret) {
+			DEBUG(1,("could not krb5_kt_close: %s\n",error_message(ret)));
+			return -1;
+		}
+
+		return 0;
+	}
+	return net_run_function(argc, argv, func, net_ads_keytab_usage);
+}
+
 int net_ads_help(int argc, const char **argv)
 {
 	struct functable func[] = {
@@ -1270,6 +1552,7 @@
 		{"WORKGROUP", net_ads_workgroup},
 		{"LOOKUP", net_ads_lookup},
 		{"HELP", net_ads_help},
+		{"KEYTAB", net_ads_keytab},
 		{NULL, NULL}
 	};
 	
@@ -1314,6 +1597,11 @@
 	return net_ads_noads();
 }
 
+int net_ads_keytab(int argc, const char **argv)
+{
+	return net_ads_noads();
+}
+
 /* this one shouldn't display a message */
 int net_ads_check(void)
 {
--- source/utils/net.h.ORIG	2003-11-07 12:37:40.000000000 -0500
+++ source/utils/net.h	2004-01-20 08:06:29.746618000 -0500
@@ -42,6 +42,8 @@
 extern int opt_flags;
 
 extern const char *opt_comment;
+extern const char *opt_keytab_file;
+extern int opt_keytab_update;
 
 extern const char *opt_target_workgroup;
 extern const char *opt_workgroup;
--- source/configure.in.ORIG	2004-01-16 12:47:52.000000000 -0500
+++ source/configure.in	2004-01-20 08:06:29.786599000 -0500
@@ -2610,6 +2610,7 @@
   # now see if we can find the krb5 libs in standard paths
   # or as specified above
   AC_CHECK_LIB_EXT(krb5, KRB5_LIBS, krb5_mk_req_extended)
+  AC_CHECK_LIB_EXT(krb5, KRB5_LIBS, krb5_kt_compare)
 
   ########################################################
   # now see if we can find the gssapi libs in standard paths
@@ -2714,6 +2715,18 @@
               [Whether the AP_OPTS_USE_SUBKEY ap option is available])
   fi
 
+  AC_CACHE_CHECK([for KV5M_KEYTAB],
+                 samba_cv_HAVE_KV5M_KEYTAB,[
+    AC_TRY_COMPILE([#include <krb5.h>],
+      [krb5_keytab_entry entry; entry.magic = KV5M_KEYTAB;],
+      samba_cv_HAVE_KV5M_KEYTAB=yes,
+      samba_cv_HAVE_KV5M_KEYTAB=no)])
+
+  if test x"$samba_cv_HAVE_KV5M_KEYTAB" = x"yes"; then
+    AC_DEFINE(HAVE_KV5M_KEYTAB,1,
+              [Whether the KV5M_KEYTAB option is available])
+  fi
+
   AC_CACHE_CHECK([for the krb5_princ_component macro],
                 samba_cv_HAVE_KRB5_PRINC_COMPONENT,[
     AC_TRY_LINK([#include <krb5.h>],
@@ -2873,6 +2886,27 @@
   AC_MSG_RESULT(no)
 )
 
+  AC_CACHE_CHECK([for WRFILE: keytab support],
+		 samba_cv_HAVE_WRFILE_KEYTAB,[
+    AC_TRY_RUN([
+#include<krb5.h>
+  main()
+  {
+    krb5_context context;
+    krb5_keytab keytab;
+    
+    krb5_init_context(&context);
+    if (krb5_kt_resolve(context, "WRFILE:/tmp/whatever", &keytab))
+      exit(0);
+    exit(1);
+  }], 
+  samba_cv_HAVE_WRFILE_KEYTAB=no,
+  samba_cv_HAVE_WRFILE_KEYTAB=yes)])
+
+  if test x"$samba_cv_HAVE_WRFILE_KEYTAB" = x"yes"; then
+      AC_DEFINE(HAVE_WRFILE_KEYTAB,1,
+               [Whether the WRFILE:-keytab is supported])
+  fi
 
 #################################################
 # check for a PAM clear-text auth, accounts, password and session support


More information about the samba-technical mailing list