4a95046cdf3358ca7d1f1a6d9029a5122f0ab02b
[openafs.git] / src / WINNT / netidmgr_plugin / krb5common.c
1 /*
2  * Copyright (c) 2005 Massachusetts Institute of Technology
3  *
4  * Permission is hereby granted, free of charge, to any person
5  * obtaining a copy of this software and associated documentation
6  * files (the "Software"), to deal in the Software without
7  * restriction, including without limitation the rights to use, copy,
8  * modify, merge, publish, distribute, sublicense, and/or sell copies
9  * of the Software, and to permit persons to whom the Software is
10  * furnished to do so, subject to the following conditions:
11  *
12  * The above copyright notice and this permission notice shall be
13  * included in all copies or substantial portions of the Software.
14  *
15  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18  * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
19  * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
20  * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
21  * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22  * SOFTWARE.
23  */
24
25 /* $Id$ */
26
27 #include<windows.h>
28 #include<netidmgr.h>
29 #include<dynimport.h>
30 #include<krb5common.h>
31 #ifdef DEBUG
32 #include<assert.h>
33 #endif
34 #include<strsafe.h>
35
36 /**************************************/
37 /* khm_krb5_error():           */
38 /**************************************/
39 int 
40 khm_krb5_error(krb5_error_code rc, LPCSTR FailedFunctionName, 
41                  int FreeContextFlag, krb5_context * ctx, 
42                  krb5_ccache * cache)
43 {
44 #ifdef NO_KRB5
45     return 0;
46 #else
47
48 #ifdef SHOW_MESSAGE_IN_AN_ANNOYING_WAY
49     char message[256];
50     const char *errText;
51     int krb5Error = ((int)(rc & 255));  
52
53     errText = pkrb5_get_error_message(rc);
54     _snprintf(message, sizeof(message), 
55         "%s\n(Kerberos error %ld)\n\n%s failed", 
56         errText, 
57         krb5Error, 
58         FailedFunctionName);
59     pkrb5_free_error_message(errText);
60
61     MessageBoxA(NULL, message, "Kerberos Five", MB_OK | MB_ICONERROR | 
62         MB_TASKMODAL | 
63         MB_SETFOREGROUND);
64 #endif
65
66     if (FreeContextFlag == 1)
67     {
68         if (*ctx != NULL)
69         {
70             if (*cache != NULL) {
71                 pkrb5_cc_close(*ctx, *cache);
72                 *cache = NULL;
73             }
74
75             pkrb5_free_context(*ctx);
76             *ctx = NULL;
77         }
78     }
79
80     return rc;
81
82 #endif //!NO_KRB5
83 }
84
85 int 
86 khm_krb5_initialize(khm_handle ident, 
87                     krb5_context *ctx, 
88                     krb5_ccache *cache)
89 {
90 #ifdef NO_KRB5
91     return(0);
92 #else
93
94     LPCSTR          functionName;
95     int             freeContextFlag;
96     krb5_error_code     rc = 0;
97     krb5_flags          flags = KRB5_TC_OPENCLOSE;
98
99     if (pkrb5_init_context == NULL)
100         return 1;
101
102     if (*ctx == 0 && (rc = (*pkrb5_init_context)(ctx))) {
103         functionName = "krb5_init_context()";
104         freeContextFlag = 0;
105         goto on_error;
106     }
107
108     if(*cache == 0) {
109         wchar_t wccname[MAX_PATH];
110         khm_size cbwccname;
111
112         if(ident != NULL) {
113             cbwccname = sizeof(wccname);
114             do {
115                 char ccname[256];
116
117                 if(KHM_FAILED(kcdb_identity_get_attrib(ident, L"Krb5CCName",
118                                                        NULL, wccname,
119                                                        &cbwccname))) {
120                     cbwccname = sizeof(wccname);
121                     if (KHM_FAILED
122                         (khm_krb5_find_ccache_for_identity(ident,
123                                                            ctx,
124                                                            wccname,
125                                                            &cbwccname))) {
126 #ifdef DEBUG_LIKE_A_MADMAN
127                         assert(FALSE);
128 #endif
129                         break;
130                     }
131                 }
132
133                 if(UnicodeStrToAnsi(ccname, sizeof(ccname), wccname) == 0)
134                     break;
135
136                 if((*pkrb5_cc_resolve)(*ctx, ccname, cache)) {
137                     functionName = "krb5_cc_resolve()";
138                     freeContextFlag = 1;
139                     goto on_error;
140                 }
141             } while(FALSE);
142         }
143
144 #ifndef FAILOVER_TO_DEFAULT_CCACHE
145         rc = 1;
146 #endif
147         if (*cache == 0
148 #ifdef FAILOVER_TO_DEFAULT_CCACHE
149             && (rc = (*pkrb5_cc_default)(*ctx, cache))
150 #endif
151             ) {
152             functionName = "krb5_cc_default()";
153             freeContextFlag = 1;
154             goto on_error;
155         }
156     }
157
158 #ifdef KRB5_TC_NOTICKET
159     flags = KRB5_TC_NOTICKET;
160 #endif
161
162     if ((rc = (*pkrb5_cc_set_flags)(*ctx, *cache, flags)))
163     {
164         if (rc != KRB5_FCC_NOFILE && rc != KRB5_CC_NOTFOUND)
165             khm_krb5_error(rc, "krb5_cc_set_flags()", 0, ctx, 
166             cache);
167         else if ((rc == KRB5_FCC_NOFILE || rc == KRB5_CC_NOTFOUND) && *ctx != NULL) {
168             if (*cache != NULL)
169                 (*pkrb5_cc_close)(*ctx, *cache);
170         }
171         return rc;
172     }
173     return 0;
174
175 on_error:
176     return khm_krb5_error(rc, functionName, freeContextFlag, ctx, cache);
177 #endif //!NO_KRB5
178 }
179
180 #define TIMET_TOLERANCE (60*5)
181
182 khm_int32 KHMAPI
183 khm_get_identity_expiration_time(krb5_context ctx, krb5_ccache cc, 
184                                  khm_handle ident, 
185                                  krb5_timestamp * pexpiration)
186 {
187     krb5_principal principal = 0;
188     char * princ_name = NULL;
189     krb5_creds creds;
190     krb5_error_code code;
191     krb5_error_code cc_code;
192     krb5_cc_cursor cur;
193     krb5_timestamp now, expiration = 0;
194
195     wchar_t w_ident_name[KCDB_IDENT_MAXCCH_NAME];
196     char    ident_name[KCDB_IDENT_MAXCCH_NAME];
197     khm_size cb;
198
199     khm_int32 rv = KHM_ERROR_NOT_FOUND;
200
201     if (!ctx || !cc || !ident || !pexpiration)
202         return KHM_ERROR_GENERAL;
203
204     code = pkrb5_cc_get_principal(ctx, cc, &principal);
205
206     if ( code )
207         return KHM_ERROR_INVALID_PARAM;
208
209     cb = sizeof(w_ident_name);
210     kcdb_identity_get_name(ident, w_ident_name, &cb);
211     UnicodeStrToAnsi(ident_name, sizeof(ident_name), w_ident_name);
212
213     code = pkrb5_unparse_name(ctx, principal, &princ_name);
214
215     /* compare principal to ident. */
216
217     if ( code || !princ_name ||
218          strcmp(princ_name, ident_name) ) {
219         if (princ_name)
220             pkrb5_free_unparsed_name(ctx, princ_name);
221         pkrb5_free_principal(ctx, principal);
222         return KHM_ERROR_UNKNOWN;
223     }
224
225     pkrb5_free_unparsed_name(ctx, princ_name);
226     pkrb5_free_principal(ctx, principal);
227
228     code = pkrb5_timeofday(ctx, &now);
229
230     if (code)
231         return KHM_ERROR_UNKNOWN;
232
233     cc_code = pkrb5_cc_start_seq_get(ctx, cc, &cur);
234
235     while (!(cc_code = pkrb5_cc_next_cred(ctx, cc, &cur, &creds))) {
236         krb5_data * c0 = krb5_princ_name(ctx, creds.server);
237         krb5_data * c1  = krb5_princ_component(ctx, creds.server, 1);
238         krb5_data * r = krb5_princ_realm(ctx, creds.server);
239
240         if ( c0 && c1 && r && c1->length == r->length && 
241              !strncmp(c1->data,r->data,r->length) &&
242              !strncmp("krbtgt",c0->data,c0->length) ) {
243
244             /* we have a TGT, check for the expiration time.
245              * if it is valid and renewable, use the renew time 
246              */
247
248             if (!(creds.ticket_flags & TKT_FLG_INVALID) &&
249                 creds.times.starttime < (now + TIMET_TOLERANCE) && 
250                 (creds.times.endtime + TIMET_TOLERANCE) > now) {
251                 expiration = creds.times.endtime;
252
253                 if ((creds.ticket_flags & TKT_FLG_RENEWABLE) && 
254                     (creds.times.renew_till > creds.times.endtime)) {
255                     expiration = creds.times.renew_till;
256                 }
257             }
258         }
259     }
260
261     if (cc_code == KRB5_CC_END) {
262         cc_code = pkrb5_cc_end_seq_get(ctx, cc, &cur);
263         rv = KHM_ERROR_SUCCESS;
264         *pexpiration = expiration;
265     }
266
267     return rv;
268 }
269
270 khm_int32 KHMAPI
271 khm_krb5_find_ccache_for_identity(khm_handle ident, krb5_context *pctx,
272                                   void * buffer, khm_size * pcbbuf)
273 {
274     krb5_context        ctx = 0;
275     krb5_ccache         cache = 0;
276     krb5_error_code     code;
277     apiCB *             cc_ctx = 0;
278     struct _infoNC **   pNCi = NULL;
279     int                 i;
280     khm_int32           t;
281     wchar_t *           ms = NULL;
282     khm_size            cb;
283     krb5_timestamp      expiration = 0;
284     krb5_timestamp      best_match_expiration = 0;
285     char                best_match_ccname[256] = "";
286     khm_handle          csp_params = NULL;
287     khm_handle          csp_plugins = NULL;
288
289     if (!buffer || !pcbbuf)
290     return KHM_ERROR_GENERAL;
291
292     ctx = *pctx;
293
294     if (!pcc_initialize ||
295         !pcc_get_NC_info ||
296         !pcc_free_NC_info ||
297         !pcc_shutdown)
298         goto _skip_cc_iter;
299
300     code = pcc_initialize(&cc_ctx, CC_API_VER_2, NULL, NULL);
301     if (code)
302         goto _exit;
303
304     code = pcc_get_NC_info(cc_ctx, &pNCi);
305
306     if (code) 
307         goto _exit;
308
309     for(i=0; pNCi[i]; i++) {
310         if (pNCi[i]->vers != CC_CRED_V5)
311             continue;
312
313         code = (*pkrb5_cc_resolve)(ctx, pNCi[i]->name, &cache);
314         if (code)
315             continue;
316
317         /* need a function to check the cache for the identity
318          * and determine if it has valid tickets.  If it has 
319          * the right identity and valid tickets, store the 
320          * expiration time and the cache name.  If it has the
321          * right identity but no valid tickets, store the ccache
322          * name and an expiration time of zero.  if it does not
323          * have the right identity don't save the name.
324          * 
325          * Keep searching to find the best cache available.
326          */
327
328         if (KHM_SUCCEEDED(khm_get_identity_expiration_time(ctx, cache, 
329                                                            ident, 
330                                                            &expiration))) {
331             if ( expiration > best_match_expiration ) {
332                 best_match_expiration = expiration;
333                 StringCbCopyA(best_match_ccname, 
334                               sizeof(best_match_ccname),
335                               "API:");
336                 StringCbCatA(best_match_ccname,
337                              sizeof(best_match_ccname),
338                              pNCi[i]->name);
339                 expiration = 0;
340             }
341         }
342
343         if(ctx != NULL && cache != NULL)
344             (*pkrb5_cc_close)(ctx, cache);
345         cache = 0;
346     }
347
348  _skip_cc_iter:
349
350     if (KHM_SUCCEEDED(kmm_get_plugins_config(0, &csp_plugins))) {
351         khc_open_space(csp_plugins, L"Krb5Cred\\Parameters",  0, &csp_params);
352         khc_close_space(csp_plugins);
353         csp_plugins = NULL;
354     }
355
356 #ifdef DEBUG
357     if (csp_params == NULL) {
358         assert(FALSE);
359     }
360 #endif
361
362     if (csp_params &&
363         KHM_SUCCEEDED(khc_read_int32(csp_params, L"MsLsaList", &t)) && t) {
364         code = (*pkrb5_cc_resolve)(ctx, "MSLSA:", &cache);
365         if (code == 0 && cache) {
366             if (KHM_SUCCEEDED(khm_get_identity_expiration_time(ctx, cache, 
367                                                                ident, 
368                                                                &expiration))) {
369                 if ( expiration > best_match_expiration ) {
370                     best_match_expiration = expiration;
371                     StringCbCopyA(best_match_ccname, sizeof(best_match_ccname),
372                                   "MSLSA:");
373                     expiration = 0;
374                 }
375             }
376         }
377
378         if (ctx != NULL && cache != NULL)
379             (*pkrb5_cc_close)(ctx, cache);
380
381         cache = 0;
382     }
383
384     if (csp_params &&
385         khc_read_multi_string(csp_params, L"FileCCList", NULL, &cb)
386         == KHM_ERROR_TOO_LONG &&
387         cb > sizeof(wchar_t) * 2) {
388
389         wchar_t * t;
390         char ccname[MAX_PATH + 6];
391
392         ms = PMALLOC(cb);
393
394 #ifdef DEBUG
395         assert(ms);
396 #endif
397
398         khc_read_multi_string(csp_params, L"FileCCList", ms, &cb);
399         for(t = ms; t && *t; t = multi_string_next(t)) {
400             StringCchPrintfA(ccname, ARRAYLENGTH(ccname),
401                              "FILE:%S", t);
402
403             code = (*pkrb5_cc_resolve)(ctx, ccname, &cache);
404             if (code)
405                 continue;
406
407             if (KHM_SUCCEEDED(khm_get_identity_expiration_time(ctx, cache, 
408                                                                ident, 
409                                                                &expiration))) {
410                 if ( expiration > best_match_expiration ) {
411                     best_match_expiration = expiration;
412                     StringCbCopyA(best_match_ccname,
413                                   sizeof(best_match_ccname),
414                                   ccname);
415                     expiration = 0;
416                 }
417             }
418
419             if (ctx != NULL && cache != NULL)
420                 (*pkrb5_cc_close)(ctx, cache);
421             cache = 0;
422         }
423
424         PFREE(ms);
425     }
426  _exit:
427     if (csp_params)
428         khc_close_space(csp_params);
429
430     if (pNCi)
431         (*pcc_free_NC_info)(cc_ctx, &pNCi);
432
433     if (cc_ctx)
434         (*pcc_shutdown)(&cc_ctx);
435
436     if (best_match_ccname[0]) {
437         
438         if (*pcbbuf = AnsiStrToUnicode((wchar_t *)buffer, 
439                                        *pcbbuf,
440                                        best_match_ccname)) {
441
442             *pcbbuf = (*pcbbuf + 1) * sizeof(wchar_t);
443
444             return KHM_ERROR_SUCCESS;
445         }
446
447     }
448
449     return KHM_ERROR_GENERAL;
450 }