[PATCH] keytab management for ADS mode.

Rakesh Patel rapatel at optonline.net
Sun Jan 25 20:40:36 GMT 2004


Another version of the patch [for 3.0.2rc1] is attached. This time 
"keytab use" is implemented and
some minor bugs related to error codes which normally would not be 
called were fixed.

The "keytab use" command allows the use of an existing keytab file, 
which is important for
those utilizing a non-Windows KDC in a workgroup environment. Testing is 
required where
a Windows 2000/2003 Domain with Active Directory is utilized, but with 
external [non-Windows] KDC.

Testing in a workgroup environment and using ksetup on an XP client was 
performed and
kerberos was properly utilized, including usage with PuTTY/GSSAPI and 
using SMB/CIFS.

smbd will not store/use a machine password in this mode and will simply 
initialize machine credentials
using the host keytab.  All other uses of secrets.tdb will continue as 
normal (SID caching, etc.).

Basic configurations:

Windows2K KDC + AD:

workgroup = NAME
realm = NAME.DOMAIN
security = ads
server = Win2K-kdc-name-or-ip
keytab file = /path/to/machine/keytab
keytab update = yes

Use "net ads join" to join the Win2k domain (creates/updates keytab).
Use "net ads changetrustpw" to change the machine key and update keytab
If keytab is corrupted  use "net ads keytab create" to regenerate the 
keytab without changing the password [retains key version number].


For MIT, Heimdal or other non-Windows KDC:

workgroup = NAME
realm = NAME.DOMAIN
security = ads
server = non-windows-kdc-name-or-ip
keytab file = /path/to/machine/keytab
keytab use = yes

Use normal MIT/Heimdal mechanisms to create/update the keytab.
Needs to be tested with a Windows 2000/2003 domain using external KDC - 
[ can KDC server and AD server
configuration be separated?].  Ideally, smb.conf should support separate 
KDC/AD servers [if it does not already]
and should only register the machine in AD without generating/updating a 
machine password.

Testing with Heimdal is also necessary.

Rakesh Patel.






-------------- next part --------------
--- source/include/ads.h.ORIG	2004-01-16 12:47:52.000000000 -0500
+++ source/include/ads.h	2004-01-25 01:07:08.000000000 -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/kerberos_verify.c.ORIG	2004-01-16 12:47:52.000000000 -0500
+++ source/libads/kerberos_verify.c	2004-01-25 10:46:45.000000000 -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,17 +191,23 @@
 	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;
 	}
 
-	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;
+	if ( lp_keytab_use ) {
+	  password_s="";
+	}
+	else {
+	  password_s = secrets_fetch_machine_password(lp_workgroup(), NULL, NULL);
+	  if (!password_s) {
+	    DEBUG(1,("ads_verify_ticket: failed to fetch machine password for domain %s\n",lp_workgroup()));
+	    return NT_STATUS_LOGON_FAILURE;
+	  }
 	}
-
 	password.data = password_s;
 	password.length = strlen(password_s);
 
@@ -232,8 +236,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",
@@ -286,9 +295,48 @@
 		sret = NT_STATUS_LOGON_FAILURE;
 		goto out;
 	}
+
+	if ( lp_keytab_use ) {
+	  /* Use Keytab to initialize credentials */
+	  char *in_tkt = NULL;
+	  krb5_creds my_creds;
+	  krb5_get_init_creds_opt options;
+	  krb5_error_code code;
+	  
+	  krb5_get_init_creds_opt_init(&options);
+	  memset(&my_creds, 0, sizeof(my_creds));
+ 
+	  if ((code = krb5_copy_principal(context, host_princ, &my_creds.client))){
+	    DEBUG(1,("while copying principal (%d)",code));
+	    sret = NT_STATUS_LOGON_FAILURE;
+	    goto out;
+	  }
+                                                                                                                                                                                                                                             
+	  if ((code = krb5_copy_principal(context, host_princ, &my_creds.server))){
+	    DEBUG(1,("while copying principal (%d)",code));
+	    sret = NT_STATUS_LOGON_FAILURE;
+	    goto out;
+	  }
+                                                                                                                                                                                                                                             
+	  my_creds.times.starttime = 0;       /* start timer when request
+                                           gets to KDC */
+                                                                                                                                                                                                                                             
+	  my_creds.times.renew_till = 0;
+
+	  asprintf(&in_tkt, "krbtgt/%s@%s", lp_realm(), lp_realm());
+	  printf("using keytab to initialize credentials...\n"); 
+	  if ((code = krb5_get_init_creds_keytab(context,&my_creds, host_princ, keytab, 0, in_tkt, &options))) {
+	    DEBUG(1,("krb5_get_init_creds_keytab()  (%d)",code));
+	    sret = NT_STATUS_LOGON_FAILURE;
+	    goto out;
+	  }
+	  
+	  
+	}
 	
 	/* We need to setup a auth context with each possible encoding type in turn. */
 	for (i=0;enctypes[i];i++) {
+	       if ( ! lp_keytab_use ) {
 		if (!(key = (krb5_keyblock *)malloc(sizeof(*key)))) {
 			sret = NT_STATUS_NO_MEMORY;
 			goto out;
@@ -301,16 +349,12 @@
 		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, 
-#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] ));
@@ -350,12 +394,16 @@
 	file_save("/tmp/ticket.dat", ticket->data, ticket->length);
 #endif
 
-	get_auth_data_from_tkt(auth_data, tkt);
-
-	{
-		TALLOC_CTX *ctx = talloc_init("pac data");
-		decode_pac_data(auth_data, ctx);
-		talloc_destroy(ctx);
+	/* When using keytab use, we are not using Win2K KDC */
+	/* however, need to use security = ads to use kerberos */
+	if ( ! lp_keytab_use ) {
+	  get_auth_data_from_tkt(auth_data, tkt);
+
+	  {
+	    TALLOC_CTX *ctx = talloc_init("pac data");
+	    decode_pac_data(auth_data, ctx);
+	    talloc_destroy(ctx);
+	  }
 	}
 
 #if 0
@@ -394,7 +442,9 @@
 	if (tkt != NULL)
 		krb5_free_ticket(context, tkt);
 	free_kerberos_etypes(context, enctypes);
-	SAFE_FREE(password_s);
+	if ( !lp_keytab_use) { 
+	  SAFE_FREE(password_s);
+	}
 	SAFE_FREE(host_princ_s);
 
 	if (auth_context)
@@ -406,4 +456,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)",code));
+	    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)",code));
+	    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)",code));
+	  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)",code));
+	  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/libads/ldap.c.ORIG	2004-01-06 16:08:40.000000000 -0500
+++ source/libads/ldap.c	2004-01-25 01:07:08.000000000 -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/param/loadparm.c.ORIG	2004-01-16 12:47:53.000000000 -0500
+++ source/param/loadparm.c	2004-01-25 08:58:32.000000000 -0500
@@ -119,6 +119,9 @@
 	char *szPasswdChat;
 	char *szLogFile;
 	char *szConfigFile;
+	char *szKeytabFile;
+	BOOL bKeytabUpdate;
+	BOOL bKeytabUse;
 	char *szSMBPasswdFile;
 	char *szPrivateDir;
 	char **szPassdbBackend;
@@ -1086,6 +1089,9 @@
 	{"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}, 
+	{"keytab use", P_BOOL, P_GLOBAL, &Globals.bKeytabUse, 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 +1532,9 @@
 	Globals.server_signing = False;
 
 	string_set(&Globals.smb_ports, SMB_PORTS);
+	
+	Globals.bKeytabUpdate = False;
+	Globals.bKeytabUse = False;
 }
 
 static TALLOC_CTX *lp_talloc;
@@ -1614,6 +1623,9 @@
 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_BOOL(lp_keytab_use, &Globals.bKeytabUse)
 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-25 01:07:08.000000000 -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-25 01:07:08.000000000 -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-25 01:07:08.000000000 -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-25 01:07:08.000000000 -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-25 01:07:08.000000000 -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