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