1/* $OpenLDAP$ */
2/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
3 *
4 * Copyright 2010-2011 The OpenLDAP Foundation.
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted only as authorized by the OpenLDAP
9 * Public License.
10 *
11 * A copy of this license is available in the file LICENSE in the
12 * top-level directory of the distribution or, alternatively, at
13 * <http://www.OpenLDAP.org/license.html>.
14 */
15
16#include <portable.h>
17
18#ifndef SLAPD_MOD_KINIT
19#define SLAPD_MOD_KINIT SLAPD_MOD_DYNAMIC
20#endif
21
22#ifdef SLAPD_MOD_KINIT
23
24#include <slap.h>
25#include "ldap_rq.h"
26#include <ac/errno.h>
27#include <ac/string.h>
28#include <krb5/krb5.h>
29
30typedef struct kinit_data {
31	krb5_context ctx;
32	krb5_ccache ccache;
33	krb5_keytab keytab;
34	krb5_principal princ;
35	krb5_get_init_creds_opt *opts;
36} kinit_data;
37
38static char* principal;
39static char* kt_name;
40static kinit_data *kid;
41
42static void
43log_krb5_errmsg( krb5_context ctx, const char* func, krb5_error_code rc )
44{
45	const char* errmsg = krb5_get_error_message(ctx, rc);
46	Log2(LDAP_DEBUG_ANY, LDAP_LEVEL_ERR, "slapd-kinit: %s: %s\n", func, errmsg);
47	krb5_free_error_message(ctx, errmsg);
48	return;
49}
50
51static int
52kinit_check_tgt(kinit_data *kid, int *remaining)
53{
54	int ret=3;
55	krb5_principal princ;
56	krb5_error_code rc;
57	krb5_cc_cursor cursor;
58	krb5_creds creds;
59	char *name;
60	time_t now=time(NULL);
61
62	rc = krb5_cc_get_principal(kid->ctx, kid->ccache, &princ);
63	if (rc) {
64		log_krb5_errmsg(kid->ctx, "krb5_cc_get_principal", rc);
65		return 2;
66	} else {
67		if (!krb5_principal_compare(kid->ctx, kid->princ, princ)) {
68			Log0(LDAP_DEBUG_ANY, LDAP_LEVEL_ERR,
69					"Principal in ccache does not match requested principal\n");
70			krb5_free_principal(kid->ctx, princ);
71			return 2;
72		}
73	}
74
75	rc = krb5_cc_start_seq_get(kid->ctx, kid->ccache, &cursor);
76	if (rc) {
77		log_krb5_errmsg(kid->ctx, "krb5_cc_start_seq_get", rc);
78		krb5_free_principal(kid->ctx, princ);
79		return -1;
80	}
81
82	while (!(rc = krb5_cc_next_cred(kid->ctx, kid->ccache, &cursor, &creds))) {
83		if (krb5_is_config_principal(kid->ctx, creds.server)) {
84			krb5_free_cred_contents(kid->ctx, &creds);
85			continue;
86		}
87
88		if (creds.server->length==2 &&
89				(!strcmp(creds.server->data[0].data, "krbtgt")) &&
90				(!strcmp(creds.server->data[1].data, princ->realm.data))) {
91
92			krb5_unparse_name(kid->ctx, creds.server, &name);
93
94			*remaining = (time_t)creds.times.endtime-now;
95			if ( *remaining <= 0) {
96				Log1(LDAP_DEBUG_TRACE, LDAP_LEVEL_DEBUG,
97						"kinit_qtask: TGT (%s) expired\n", name);
98			} else {
99				Log4(LDAP_DEBUG_TRACE, LDAP_LEVEL_DEBUG,
100						"kinit_qtask: TGT (%s) expires in %dh:%02dm:%02ds\n",
101						name, *remaining/3600, (*remaining%3600)/60, *remaining%60);
102			}
103			free(name);
104
105			if (*remaining <= 30) {
106				if (creds.times.renew_till-60 > now) {
107					int renewal=creds.times.renew_till-now;
108					Log3(LDAP_DEBUG_TRACE, LDAP_LEVEL_DEBUG,
109							"kinit_qtask: Time remaining for renewal: %dh:%02dm:%02ds\n",
110							renewal/3600, (renewal%3600)/60,  renewal%60);
111					ret = 1;
112				} else {
113					Log0(LDAP_DEBUG_TRACE, LDAP_LEVEL_DEBUG,
114							"kinit_qtask: Only short time left for renewal. "
115							"Trying to re-init.\n");
116					ret = 2;
117				}
118			} else {
119				ret=0;
120			}
121			krb5_free_cred_contents(kid->ctx, &creds);
122			break;
123		}
124		krb5_free_cred_contents(kid->ctx, &creds);
125
126	}
127	krb5_cc_end_seq_get(kid->ctx, kid->ccache, &cursor);
128	krb5_free_principal(kid->ctx, princ);
129	return ret;
130}
131
132void*
133kinit_qtask( void *ctx, void *arg )
134{
135	struct re_s     *rtask = arg;
136	kinit_data	*kid = (kinit_data*)rtask->arg;
137	krb5_error_code rc;
138	krb5_creds creds;
139	int nextcheck, remaining, renew=0;
140	Log0(LDAP_DEBUG_TRACE, LDAP_LEVEL_DEBUG, "kinit_qtask: running TGT check\n");
141
142	memset(&creds, 0, sizeof(creds));
143
144	renew = kinit_check_tgt(kid, &remaining);
145
146	if (renew > 0) {
147		if (renew==1) {
148			Log0(LDAP_DEBUG_TRACE, LDAP_LEVEL_DEBUG,
149					"kinit_qtask: Trying to renew TGT: ");
150			rc = krb5_get_renewed_creds(kid->ctx, &creds, kid->princ, kid->ccache, NULL);
151			if (rc!=0) {
152				Log0(LDAP_DEBUG_TRACE, LDAP_LEVEL_DEBUG, "Failed\n");
153				log_krb5_errmsg( kid->ctx,
154						"kinit_qtask, Renewal failed: krb5_get_renewed_creds", rc );
155				renew++;
156			} else {
157				Log0(LDAP_DEBUG_TRACE, LDAP_LEVEL_DEBUG, "Success\n");
158				krb5_cc_initialize(kid->ctx, kid->ccache, creds.client);
159				krb5_cc_store_cred(kid->ctx, kid->ccache, &creds);
160				krb5_free_cred_contents(kid->ctx, &creds);
161				renew=kinit_check_tgt(kid, &remaining);
162			}
163		}
164		if (renew > 1) {
165			Log0(LDAP_DEBUG_TRACE, LDAP_LEVEL_DEBUG,
166					"kinit_qtask: Trying to get new TGT: ");
167			rc = krb5_get_init_creds_keytab( kid->ctx, &creds, kid->princ,
168					kid->keytab, 0, NULL, kid->opts);
169			if (rc) {
170				Log0(LDAP_DEBUG_TRACE, LDAP_LEVEL_DEBUG, "Failed\n");
171				log_krb5_errmsg(kid->ctx, "krb5_get_init_creds_keytab", rc);
172			} else {
173				Log0(LDAP_DEBUG_TRACE, LDAP_LEVEL_DEBUG, "Success\n");
174				renew=kinit_check_tgt(kid, &remaining);
175			}
176			krb5_free_cred_contents(kid->ctx, &creds);
177		}
178	}
179	if (renew == 0) {
180		nextcheck = remaining-30;
181	} else {
182		nextcheck = 60;
183	}
184
185	ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex );
186	if ( ldap_pvt_runqueue_isrunning( &slapd_rq, rtask )) {
187		ldap_pvt_runqueue_stoptask( &slapd_rq, rtask );
188	}
189	Log3(LDAP_DEBUG_TRACE, LDAP_LEVEL_DEBUG,
190			"kinit_qtask: Next TGT check in %dh:%02dm:%02ds\n",
191			nextcheck/3600, (nextcheck%3600)/60,  nextcheck%60);
192	rtask->interval.tv_sec = nextcheck;
193	ldap_pvt_runqueue_resched( &slapd_rq, rtask, 0 );
194	slap_wake_listener();
195	ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex );
196	return NULL;
197}
198
199int
200kinit_initialize(void)
201{
202	Log0( LDAP_DEBUG_TRACE, LDAP_LEVEL_DEBUG, "kinit_initialize\n" );
203	krb5_error_code rc;
204	struct re_s *task = NULL;
205
206	kid = ch_calloc(1, sizeof(kinit_data) );
207
208	rc = krb5_init_context( &kid->ctx );
209	if ( !rc )
210		rc = krb5_cc_default(kid->ctx, &kid->ccache );
211
212	if ( !rc ) {
213		if (!principal) {
214			int len=STRLENOF("ldap/")+global_host_bv.bv_len+1;
215			principal=ch_calloc(len, 1);
216			snprintf(principal, len, "ldap/%s", global_host_bv.bv_val);
217			Log1(LDAP_DEBUG_TRACE, LDAP_LEVEL_DEBUG, "Principal <%s>\n", principal);
218
219		}
220		rc = krb5_parse_name(kid->ctx, principal, &kid->princ);
221	}
222
223	if ( !rc && kt_name) {
224		rc = krb5_kt_resolve(kid->ctx, kt_name, &kid->keytab);
225	}
226
227	if ( !rc )
228		rc = krb5_get_init_creds_opt_alloc(kid->ctx, &kid->opts);
229
230	if ( !rc )
231		rc = krb5_get_init_creds_opt_set_out_ccache( kid->ctx, kid->opts, kid->ccache);
232
233	if ( !rc ) {
234		ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex );
235		task = ldap_pvt_runqueue_insert( &slapd_rq, 10, kinit_qtask, (void*)kid,
236				"kinit_qtask", "ldap/bronsted.g17.lan@G17.LAN" );
237		ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex );
238	}
239
240	if (rc) {
241		log_krb5_errmsg(kid->ctx, "kinit_initialize", rc);
242		rc = -1;
243	}
244	return rc;
245}
246
247#if SLAPD_MOD_KINIT == SLAPD_MOD_DYNAMIC
248int init_module(int argc, char *argv[]) {
249	if (argc > 0) {
250		principal = ch_strdup(argv[0]);
251	}
252	if (argc > 1) {
253		kt_name = ch_strdup(argv[1]);
254	}
255	if (argc > 2) {
256		return -1;
257	}
258	return kinit_initialize();
259}
260
261int
262term_module() {
263	if (principal)
264		ch_free(principal);
265	if (kt_name)
266		ch_free(kt_name);
267	if (kid) {
268		struct re_s *task;
269
270		task=ldap_pvt_runqueue_find( &slapd_rq, kinit_qtask, (void*)kid);
271		if (task) {
272			if ( ldap_pvt_runqueue_isrunning(&slapd_rq, task) ) {
273				ldap_pvt_runqueue_stoptask(&slapd_rq, task);
274			}
275			ldap_pvt_runqueue_remove(&slapd_rq, task);
276		}
277		if ( kid->ctx ) {
278			if ( kid->princ )
279				krb5_free_principal(kid->ctx, kid->princ);
280			if ( kid->ccache )
281				krb5_cc_close(kid->ctx, kid->ccache);
282			if ( kid->keytab )
283				krb5_kt_close(kid->ctx, kid->keytab);
284			if ( kid->opts )
285				krb5_get_init_creds_opt_free(kid->ctx, kid->opts);
286			krb5_free_context(kid->ctx);
287		}
288		ch_free(kid);
289	}
290	return 0;
291}
292#endif
293
294#endif /* SLAPD_MOD_KINIT */
295
296