1 /* $OpenLDAP$ */
2 /* This work is part of OpenLDAP Software <http://www.openldap.org/>.
3  *
4  * Copyright 2010-2021 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 
30 typedef 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 
38 static char* principal;
39 static char* kt_name;
40 static kinit_data *kid;
41 
42 static void
log_krb5_errmsg(krb5_context ctx,const char * func,krb5_error_code rc)43 log_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 
51 static int
kinit_check_tgt(kinit_data * kid,int * remaining)52 kinit_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 
132 void*
kinit_qtask(void * ctx,void * arg)133 kinit_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 
199 int
kinit_initialize(void)200 kinit_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
init_module(int argc,char * argv[])248 int 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 
261 int
term_module()262 term_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