Backport of the clean event context after fork and
[metze/samba/wip.git] / source / nsswitch / winbindd_cred_cache.c
1 /*
2    Unix SMB/CIFS implementation.
3
4    Winbind daemon - krb5 credential cache functions
5    and in-memory cache functions.
6
7    Copyright (C) Guenther Deschner 2005-2006
8    Copyright (C) Jeremy Allison 2006
9    
10    This program is free software; you can redistribute it and/or modify
11    it under the terms of the GNU General Public License as published by
12    the Free Software Foundation; either version 2 of the License, or
13    (at your option) any later version.
14    
15    This program is distributed in the hope that it will be useful,
16    but WITHOUT ANY WARRANTY; without even the implied warranty of
17    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18    GNU General Public License for more details.
19    
20    You should have received a copy of the GNU General Public License
21    along with this program; if not, write to the Free Software
22    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
23 */
24
25 #include "includes.h"
26 #include "winbindd.h"
27 #undef DBGC_CLASS
28 #define DBGC_CLASS DBGC_WINBIND
29
30 /* uncomment this to to fast debugging on the krb5 ticket renewal event */
31 #ifdef DEBUG_KRB5_TKT_RENEWAL
32 #undef DEBUG_KRB5_TKT_RENEWAL
33 #endif
34
35 #define MAX_CCACHES 100
36
37 static struct WINBINDD_CCACHE_ENTRY *ccache_list;
38 static void add_krb5_ticket_gain_handler_event(struct WINBINDD_CCACHE_ENTRY *entry,
39                                                 struct timeval t);
40
41 /* The Krb5 ticket refresh handler should be scheduled 
42    at one-half of the period from now till the tkt 
43    expiration */
44 #define KRB5_EVENT_REFRESH_TIME(x) ((x) - (((x) - time(NULL))/2))
45
46 /****************************************************************
47  Find an entry by name.
48 ****************************************************************/
49
50 static struct WINBINDD_CCACHE_ENTRY *get_ccache_by_username(const char *username)
51 {
52         struct WINBINDD_CCACHE_ENTRY *entry;
53
54         for (entry = ccache_list; entry; entry = entry->next) {
55                 if (strequal(entry->username, username)) {
56                         return entry;
57                 }
58         }
59         return NULL;
60 }
61
62 /****************************************************************
63  How many do we have ?
64 ****************************************************************/
65
66 static int ccache_entry_count(void)
67 {
68         struct WINBINDD_CCACHE_ENTRY *entry;
69         int i = 0;
70
71         for (entry = ccache_list; entry; entry = entry->next) {
72                 i++;
73         }
74         return i;
75 }
76
77 void ccache_remove_all_after_fork(void)
78 {
79         struct WINBINDD_CCACHE_ENTRY *cur;
80         cur = ccache_list;
81         while (cur) {
82                 DLIST_REMOVE(ccache_list, cur);
83                 TALLOC_FREE(cur->event);
84                 TALLOC_FREE(cur);
85                 cur = ccache_list;
86         }
87 }
88
89 static void krb5_ticket_gain_handler(struct event_context *event_ctx,
90                                      struct timed_event *te,
91                                      const struct timeval *now,
92                                      void *private_data);
93 static void krb5_ticket_refresh_handler(struct event_context *event_ctx,
94                                         struct timed_event *te,
95                                         const struct timeval *now,
96                                         void *private_data);
97
98 void ccache_regain_all_now(void)
99 {
100         struct WINBINDD_CCACHE_ENTRY *cur;
101         struct timeval t = timeval_current();
102
103         cur = ccache_list;
104         while (cur) {
105                 TALLOC_FREE(cur->event);
106                 if (cur->refresh_time) {
107                         cur->event = event_add_timed(winbind_event_context(),
108                                                      cur, t,
109                                                      "krb5_ticket_refresh_handler",
110                                                      krb5_ticket_refresh_handler,
111                                                      cur);
112                 } else {
113                         cur->event = event_add_timed(winbind_event_context(),
114                                                       cur, t,
115                                                       "krb5_ticket_gain_handler",
116                                                       krb5_ticket_gain_handler,
117                                                       cur);
118                 }
119                 if (!cur->event) {
120                         DEBUG(0, ("ccache_regain_all_now: out of memory!!\n"));
121                 }
122         }
123         return;
124 }
125
126 /****************************************************************
127  The gain initial ticket is recognized as entry->refresh_time is
128  always zero.
129 ****************************************************************/
130
131 static void add_krb5_ticket_gain_handler_event(struct WINBINDD_CCACHE_ENTRY *entry,
132                                                 struct timeval t)
133 {
134         entry->refresh_time = 0;
135         entry->event = event_add_timed(winbind_event_context(), entry,
136                                         t,
137                                         "krb5_ticket_gain_handler",
138                                         krb5_ticket_gain_handler,
139                                         entry);
140 }
141
142 /****************************************************************
143  Do the work of refreshing the ticket.
144 ****************************************************************/
145
146 static void krb5_ticket_refresh_handler(struct event_context *event_ctx,
147                                         struct timed_event *te,
148                                         const struct timeval *now,
149                                         void *private_data)
150 {
151         struct WINBINDD_CCACHE_ENTRY *entry =
152                 talloc_get_type_abort(private_data, struct WINBINDD_CCACHE_ENTRY);
153 #ifdef HAVE_KRB5
154         int ret;
155         time_t new_start;
156         time_t expire_time = 0;
157         struct WINBINDD_MEMORY_CREDS *cred_ptr = entry->cred_ptr;
158 #endif
159
160         DEBUG(10,("krb5_ticket_refresh_handler called\n"));
161         DEBUGADD(10,("event called for: %s, %s\n", entry->ccname, entry->username));
162
163         TALLOC_FREE(entry->event);
164
165 #ifdef HAVE_KRB5
166
167         /* Kinit again if we have the user password and we can't renew the old
168          * tgt anymore
169          * NB
170          * This happens when machine are put to sleep for a very long time. */
171
172         if (entry->renew_until < time(NULL)) {
173 rekinit:
174                 if (cred_ptr && cred_ptr->pass) {
175
176                         set_effective_uid(entry->uid);
177
178                         ret = kerberos_kinit_password_ext(entry->principal_name,
179                                                           cred_ptr->pass,
180                                                           0, /* hm, can we do time correction here ? */
181                                                           &entry->refresh_time,
182                                                           &entry->renew_until,
183                                                           entry->ccname,
184                                                           False, /* no PAC required anymore */
185                                                           True,
186                                                           WINBINDD_PAM_AUTH_KRB5_RENEW_TIME);
187                         gain_root_privilege();
188
189                         if (ret) {
190                                 DEBUG(3,("krb5_ticket_refresh_handler: "
191                                         "could not re-kinit: %s\n",
192                                         error_message(ret)));
193                                 /* destroy the ticket because we cannot rekinit
194                                  * it, ignore error here */
195                                 ads_kdestroy(entry->ccname);
196
197                                 /* Don't break the ticket refresh chain: retry 
198                                  * refreshing ticket sometime later when KDC is 
199                                  * unreachable -- BoYang.
200                                  * More error handling here? KRB5_CC_IO, 
201                                  * KRB5KRB_AP_ERR_SKEW.
202                                  * */
203
204                                 if ((ret == KRB5_KDC_UNREACH)
205                                     || (ret == KRB5_REALM_CANT_RESOLVE)) {
206 #if defined(DEBUG_KRB5_TKT_RENEWAL)
207                                         new_start = time(NULL) + 30;
208 #else
209                                         new_start = time(NULL) +
210                                                     MAX(30, lp_winbind_cache_time());
211 #endif
212                                         /* try to regain ticket here */
213                                         add_krb5_ticket_gain_handler_event(entry,
214                                                                         timeval_set(new_start, 0));
215                                         return;
216                                 }
217                                 TALLOC_FREE(entry->event);
218                                 return;
219                         }
220
221                         DEBUG(10,("krb5_ticket_refresh_handler: successful re-kinit "
222                                 "for: %s in ccache: %s\n",
223                                 entry->principal_name, entry->ccname));
224   
225 #if defined(DEBUG_KRB5_TKT_RENEWAL)
226                         new_start = time(NULL) + 30;
227 #else
228                         /* The tkt should be refreshed at one-half the period
229                            from now to the expiration time */
230                         expire_time = entry->refresh_time;
231                         new_start = KRB5_EVENT_REFRESH_TIME(entry->refresh_time);
232 #endif
233                         goto done;
234                 } else {
235                                 /* can this happen? 
236                                  * No cached credentials
237                                  * destroy ticket and refresh chain 
238                                  * */
239                                 ads_kdestroy(entry->ccname);
240                                 TALLOC_FREE(entry->event);
241                                 return;
242                 }
243         }
244
245         set_effective_uid(entry->uid);
246
247         ret = smb_krb5_renew_ticket(entry->ccname, 
248                                     entry->principal_name,
249                                     entry->service,
250                                     &new_start);
251 #if defined(DEBUG_KRB5_TKT_RENEWAL)
252         new_start = time(NULL) + 30;
253 #else
254         expire_time = new_start;
255         new_start = KRB5_EVENT_REFRESH_TIME(new_start);
256 #endif
257
258         gain_root_privilege();
259
260         if (ret) {
261                 DEBUG(3,("krb5_ticket_refresh_handler: could not renew tickets: %s\n",
262                         error_message(ret)));
263                 /* maybe we are beyond the renewing window */
264
265                 /* evil rises here, we refresh ticket failed,
266                  * but the ticket might be expired. Therefore,
267                  * When we refresh ticket failed, destory the 
268                  * ticket */
269  
270                 ads_kdestroy(entry->ccname);
271  
272                 /* avoid breaking the renewal chain: retry in lp_winbind_cache_time()
273                  * seconds when the KDC was not available right now.
274                  * the return code can be KRB5_REALM_CANT_RESOLVE
275                  * More error handling here? KRB5_CC_IO, KRB5KRB_AP_ERR_SKEW. */
276   
277                 if ((ret == KRB5_KDC_UNREACH) 
278                     || (ret == KRB5_REALM_CANT_RESOLVE)) {
279 #if defined(DEBUG_KRB5_TKT_RENEWAL)
280                         new_start = time(NULL) + 30;
281 #else
282                         new_start = time(NULL) +
283                                     MAX(30, lp_winbind_cache_time());
284 #endif
285                         /* ticket is destroyed here, we have to regain it
286                          * if it is possible */
287                         add_krb5_ticket_gain_handler_event(entry, timeval_set(new_start, 0));
288                         return;
289                 }
290                 /* This is evil, if the ticket was already expired.
291                  * renew ticket function returns KRB5KRB_AP_ERR_TKT_EXPIRED.
292                  * But there is still a chance that we can rekinit it. 
293                  *
294                  * This happens when user login in online mode, and then network
295                  * down or something cause winbind goes offline for a very long time,
296                  * and then goes online again. ticket expired, renew failed.
297                  * This happens when machine are put to sleep for a long time,
298                  * but shorter than entry->renew_util.
299                  * NB
300                  * Looks like the KDC is reachable, we want to rekinit as soon as
301                  * possible instead of waiting some time later. */
302                 if ((ret == KRB5KRB_AP_ERR_TKT_EXPIRED)
303                     || (ret == KRB5_FCC_NOFILE)) goto rekinit;
304  
305
306                 return;
307         }
308
309 done:
310         /* in cases that ticket will be unrenewable soon, we don't try to renew ticket 
311          * but try to regain ticket if it is possible */
312         if (entry->renew_until && expire_time
313              && (entry->renew_until <= expire_time)) {
314                 /* try to regain ticket 10 seconds beforre expiration */
315                 expire_time -= 10;
316                 add_krb5_ticket_gain_handler_event(entry, timeval_set(expire_time, 0));
317                 return;
318         }
319         
320         if (!entry->refresh_time) {
321                 entry->refresh_time = new_start;
322         }
323         entry->event = event_add_timed(winbind_event_context(), entry, 
324                                        timeval_set(new_start, 0),
325                                        "krb5_ticket_refresh_handler",
326                                        krb5_ticket_refresh_handler,
327                                        entry);
328
329 #endif
330 }
331
332 /****************************************************************
333  Do the work of regaining a ticket when coming from offline auth.
334 ****************************************************************/
335
336 static void krb5_ticket_gain_handler(struct event_context *event_ctx,
337                                      struct timed_event *te,
338                                         const struct timeval *now,
339                                         void *private_data)
340 {
341         struct WINBINDD_CCACHE_ENTRY *entry =
342                 talloc_get_type_abort(private_data, struct WINBINDD_CCACHE_ENTRY);
343 #ifdef HAVE_KRB5
344         int ret;
345         struct timeval t;
346         struct WINBINDD_MEMORY_CREDS *cred_ptr = entry->cred_ptr;
347         struct winbindd_domain *domain = NULL;
348 #endif
349
350         DEBUG(10,("krb5_ticket_gain_handler called\n"));
351         DEBUGADD(10,("event called for: %s, %s\n", entry->ccname, entry->username));
352
353         TALLOC_FREE(entry->event);
354
355 #ifdef HAVE_KRB5
356
357         if (!cred_ptr || !cred_ptr->pass) {
358                 DEBUG(10,("krb5_ticket_gain_handler: no memory creds\n"));
359                 return;
360         }
361
362         if ((domain = find_domain_from_name(entry->realm)) == NULL) {
363                 DEBUG(0,("krb5_ticket_gain_handler: unknown domain\n"));
364                 return;
365         }
366
367         if (domain->online) {
368
369                 set_effective_uid(entry->uid);
370
371                 ret = kerberos_kinit_password_ext(entry->principal_name,
372                                                 cred_ptr->pass,
373                                                 0, /* hm, can we do time correction here ? */
374                                                 &entry->refresh_time,
375                                                 &entry->renew_until,
376                                                 entry->ccname,
377                                                 False, /* no PAC required anymore */
378                                                 True,
379                                                 WINBINDD_PAM_AUTH_KRB5_RENEW_TIME);
380                 gain_root_privilege();
381
382                 if (ret) {
383                         DEBUG(3,("krb5_ticket_gain_handler: could not kinit: %s\n",
384                                 error_message(ret)));
385                         /* evil. If we cannot do it, destroy any the __maybe__ 
386                          * __existing__ ticket */
387                         ads_kdestroy(entry->ccname);
388                         goto retry_later;
389                 }
390
391                 DEBUG(10,("krb5_ticket_gain_handler: successful kinit for: %s in ccache: %s\n",
392                         entry->principal_name, entry->ccname));
393
394                 goto got_ticket;
395         }
396
397   retry_later:
398 #if defined(DEBUG_KRB5_TKT_REGAIN)
399         t = timeval_set(time(NULL) + 30, 0);
400 #else
401         t = timeval_current_ofs(MAX(30, lp_winbind_cache_time()), 0);
402 #endif
403         add_krb5_ticket_gain_handler_event(entry, t);
404
405         return;
406
407   got_ticket:
408
409 #if defined(DEBUG_KRB5_TKT_RENEWAL)
410         t = timeval_set(time(NULL) + 30, 0);
411 #else
412         t = timeval_set(KRB5_EVENT_REFRESH_TIME(entry->refresh_time), 0);
413 #endif
414         
415         if (!entry->refresh_time) {
416                 entry->refresh_time = t.tv_sec;
417         }
418         entry->event = event_add_timed(winbind_event_context(), entry,
419                                         t,
420                                         "krb5_ticket_refresh_handler",
421                                         krb5_ticket_refresh_handler,
422                                         entry);
423
424         return;
425 #endif
426 }
427
428 /****************************************************************
429  Check if an ccache entry exists.
430 ****************************************************************/
431
432 BOOL ccache_entry_exists(const char *username)
433 {
434         struct WINBINDD_CCACHE_ENTRY *entry = get_ccache_by_username(username);
435         return (entry != NULL);
436 }
437
438 /****************************************************************
439  Ensure we're changing the correct entry.
440 ****************************************************************/
441
442 BOOL ccache_entry_identical(const char *username, uid_t uid, const char *ccname)
443 {
444         struct WINBINDD_CCACHE_ENTRY *entry = get_ccache_by_username(username);
445
446         if (!entry) {
447                 return False;
448         }
449
450         if (entry->uid != uid) {
451                 DEBUG(0,("cache_entry_identical: uid's differ: %u != %u\n",
452                         (unsigned int)entry->uid, (unsigned int)uid ));
453                 return False;
454         }
455         if (!strcsequal(entry->ccname, ccname)) {
456                 DEBUG(0,("cache_entry_identical: ccnames differ: (cache) %s != (client) %s\n",
457                         entry->ccname, ccname));
458                 return False;
459         }
460         return True;
461 }
462
463 NTSTATUS add_ccache_to_list(const char *princ_name,
464                             const char *ccname,
465                             const char *service,
466                             const char *username, 
467                             const char *realm,
468                             uid_t uid,
469                             time_t create_time, 
470                             time_t ticket_end, 
471                             time_t renew_until, 
472                             BOOL postponed_request)
473 {
474         struct WINBINDD_CCACHE_ENTRY *entry = NULL;
475         NTSTATUS ntret;
476 #ifdef HAVE_KRB5
477         int ret;
478 #endif
479
480         if ((username == NULL && princ_name == NULL) || ccname == NULL || uid < 0) {
481                 return NT_STATUS_INVALID_PARAMETER;
482         }
483
484         if (ccache_entry_count() + 1 > MAX_CCACHES) {
485                 DEBUG(10,("add_ccache_to_list: max number of ccaches reached\n"));
486                 return NT_STATUS_NO_MORE_ENTRIES;
487         }
488
489         /* If it is cached login, destroy krb5 ticket
490          * to avoid surprise. */
491 #ifdef HAVE_KRB5
492         if (postponed_request) {
493                 /* ignore KRB5_FCC_NOFILE error here */
494                 ret = ads_kdestroy(ccname);
495                 if (ret == KRB5_FCC_NOFILE) {
496                         ret = 0;
497                 }
498                 if (ret) {
499                         DEBUG(0, ("add_ccache_to_list: failed to destroy "
500                                    "user krb5 ccache %s with %s\n", ccname,
501                                    error_message(ret)));
502                         return krb5_to_nt_status(ret);
503                 } else {
504                         DEBUG(10, ("add_ccache_to_list: successfully destroyed "
505                                    "krb5 ccache %s for user %s\n", ccname,
506                                    username));
507                 }
508         }
509 #endif
510
511         /* Reference count old entries */
512         entry = get_ccache_by_username(username);
513         if (entry) {
514                 /* Check cached entries are identical. */
515                 if (!ccache_entry_identical(username, uid, ccname)) {
516                         return NT_STATUS_INVALID_PARAMETER;
517                 }
518                 entry->ref_count++;
519                 DEBUG(10,("add_ccache_to_list: ref count on entry %s is now %d\n",
520                         username, entry->ref_count));
521                 /* FIXME: in this case we still might want to have a krb5 cred
522                  * event handler created - gd
523                  * Add ticket refresh handler here */
524                 
525                 if (!lp_winbind_refresh_tickets() || renew_until <= 0) {
526                         return NT_STATUS_OK;
527                 }
528                 
529                 if (!entry->event) {
530                         struct timeval t;
531                         if (postponed_request) {
532                                 t = timeval_current_ofs(MAX(30, lp_winbind_cache_time()), 0);
533                                 add_krb5_ticket_gain_handler_event(entry, t);
534                         } else {
535                                 /* Renew at 1/2 the ticket expiration time */
536 #if defined(DEBUG_KRB5_TKT_RENEWAL)
537                                 t = timeval_set(time(NULL)+30, 0);
538 #else
539                                 t = timeval_set(KRB5_EVENT_REFRESH_TIME(ticket_end), 0);
540 #endif
541                                 if (!entry->refresh_time) {
542                                         entry->refresh_time = t.tv_sec;
543                                 }
544                                 entry->event = event_add_timed(winbind_event_context(),
545                                                                entry,
546                                                                t,
547                                                                "krb5_ticket_refresh_handler",
548                                                                krb5_ticket_refresh_handler,
549                                                                entry);
550                         }
551
552                         if (!entry->event) {
553                                 ntret = remove_ccache(username);
554                                 if (!NT_STATUS_IS_OK(ntret)) {
555                                         DEBUG(0, ("add_ccache_to_list: Failed to remove krb5 "
556                                                   "ccache %s for user %s\n", entry->ccname,
557                                                   entry->username));
558                                         DEBUG(0, ("add_ccache_to_list: error is %s\n",
559                                                   nt_errstr(ntret)));
560                                         return ntret;
561                                 }
562                                 return NT_STATUS_NO_MEMORY;
563                         }
564
565                         DEBUG(10,("add_ccache_to_list: added krb5_ticket handler\n"));
566                 }
567                  
568                 return NT_STATUS_OK;
569         }
570         
571         entry = TALLOC_P(NULL, struct WINBINDD_CCACHE_ENTRY);
572         if (!entry) {
573                 return NT_STATUS_NO_MEMORY;
574         }
575
576         ZERO_STRUCTP(entry);
577
578         if (username) {
579                 entry->username = talloc_strdup(entry, username);
580                 if (!entry->username) {
581                         goto no_mem;
582                 }
583         }
584         if (princ_name) {
585                 entry->principal_name = talloc_strdup(entry, princ_name);
586                 if (!entry->principal_name) {
587                         goto no_mem;
588                 }
589         }
590         if (service) {
591                 entry->service = talloc_strdup(entry, service);
592                 if (!entry->service) {
593                         goto no_mem;
594                 }
595         }
596
597         entry->ccname = talloc_strdup(entry, ccname);
598         if (!entry->ccname) {
599                 goto no_mem;
600         }
601
602         entry->realm = talloc_strdup(entry, realm);
603         if (!entry->realm) {
604                 goto no_mem;
605         }
606
607         entry->create_time = create_time;
608         entry->renew_until = renew_until;
609         entry->uid = uid;
610         entry->ref_count = 1;
611
612         if (lp_winbind_refresh_tickets() && renew_until > 0) {
613                 struct timeval t;
614                 if (postponed_request) {
615                         add_krb5_ticket_gain_handler_event(entry, t);
616                 } else {
617                         /* Renew at 1/2 the ticket expiration time */
618 #if defined(DEBUG_KRB5_TKT_RENEWAL)
619                         t = timeval_set(time(NULL)+30, 0);
620 #else
621                         t = timeval_set(KRB5_EVENT_REFRESH_TIME(ticket_end), 0);
622 #endif
623                         if (!entry->refresh_time) {
624                                 entry->refresh_time = t.tv_sec;
625                         }
626                         entry->event = event_add_timed(winbind_event_context(), entry,
627                                                 t,
628                                                 "krb5_ticket_refresh_handler",
629                                                 krb5_ticket_refresh_handler,
630                                                 entry);
631                 }
632
633                 if (!entry->event) {
634                         goto no_mem;
635                 }
636
637                 DEBUG(10,("add_ccache_to_list: added krb5_ticket handler\n"));
638         }
639
640         DLIST_ADD(ccache_list, entry);
641
642         DEBUG(10,("add_ccache_to_list: added ccache [%s] for user [%s] to the list\n", ccname, username));
643
644         return NT_STATUS_OK;
645
646  no_mem:
647
648         TALLOC_FREE(entry);
649         return NT_STATUS_NO_MEMORY;
650 }
651
652 /*******************************************************************
653  Remove a WINBINDD_CCACHE_ENTRY entry and the krb5 ccache if no longer referenced.
654 *******************************************************************/
655
656 NTSTATUS remove_ccache(const char *username)
657 {
658         struct WINBINDD_CCACHE_ENTRY *entry = get_ccache_by_username(username);
659         NTSTATUS status = NT_STATUS_OK;
660 #ifdef HAVE_KRB5
661         krb5_error_code ret;
662 #endif
663
664         if (!entry) {
665                 return NT_STATUS_OBJECT_NAME_NOT_FOUND;
666         }
667
668         if (entry->ref_count <= 0) {
669                 DEBUG(0,("remove_ccache: logic error. ref count for user %s = %d\n",
670                         username, entry->ref_count));
671                 return NT_STATUS_INTERNAL_DB_CORRUPTION;
672         }
673
674         entry->ref_count--;
675
676         if (entry->ref_count > 0) {
677                 DEBUG(10,("remove_ccache: entry %s ref count now %d\n",
678                         username, entry->ref_count ));
679                 return NT_STATUS_OK;
680         }
681
682         /* no references any more */
683
684         DLIST_REMOVE(ccache_list, entry);
685         TALLOC_FREE(entry->event); /* unregisters events */
686
687 #ifdef HAVE_KRB5
688         ret = ads_kdestroy(entry->ccname);
689
690         /* we ignore the error when there has been no credential cache */
691         if (ret == KRB5_FCC_NOFILE) {
692                 ret = 0;
693         } else if (ret) {
694                 DEBUG(0,("remove_ccache: failed to destroy user krb5 ccache %s with: %s\n",
695                         entry->ccname, error_message(ret)));
696         } else {
697                 DEBUG(10,("remove_ccache: successfully destroyed krb5 ccache %s for user %s\n",
698                         entry->ccname, username));
699         }
700         status = krb5_to_nt_status(ret);
701 #endif
702
703         TALLOC_FREE(entry);
704         DEBUG(10,("remove_ccache: removed ccache for user %s\n", username));
705
706         return status;
707 }
708
709 /*******************************************************************
710  In memory credentials cache code.
711 *******************************************************************/
712
713 static struct WINBINDD_MEMORY_CREDS *memory_creds_list;
714
715 /***********************************************************
716  Find an entry on the list by name.
717 ***********************************************************/
718
719 struct WINBINDD_MEMORY_CREDS *find_memory_creds_by_name(const char *username)
720 {
721         struct WINBINDD_MEMORY_CREDS *p;
722
723         for (p = memory_creds_list; p; p = p->next) {
724                 if (strequal(p->username, username)) {
725                         return p;
726                 }
727         }
728         return NULL;
729 }
730
731 /***********************************************************
732  Store the required creds and mlock them.
733 ***********************************************************/
734
735 static NTSTATUS store_memory_creds(struct WINBINDD_MEMORY_CREDS *memcredp, const char *pass)
736 {
737 #if !defined(HAVE_MLOCK)
738         return NT_STATUS_OK;
739 #else
740         /* new_entry->nt_hash is the base pointer for the block
741            of memory pointed into by new_entry->lm_hash and
742            new_entry->pass (if we're storing plaintext). */
743
744         memcredp->len = NT_HASH_LEN + LM_HASH_LEN;
745         if (pass) {
746                 memcredp->len += strlen(pass)+1;
747         }
748
749
750 #if defined(LINUX)
751         /* aligning the memory on on x86_64 and compiling 
752            with gcc 4.1 using -O2 causes a segv in the 
753            next memset()  --jerry */
754         memcredp->nt_hash = SMB_MALLOC_ARRAY(unsigned char, memcredp->len);
755 #else
756         /* On non-linux platforms, mlock()'d memory must be aligned */
757         memcredp->nt_hash = SMB_MEMALIGN_ARRAY(unsigned char, 
758                                                getpagesize(), memcredp->len);
759 #endif
760         if (!memcredp->nt_hash) {
761                 return NT_STATUS_NO_MEMORY;
762         }
763         memset( memcredp->nt_hash, 0x0, memcredp->len );
764
765         memcredp->lm_hash = memcredp->nt_hash + NT_HASH_LEN;
766
767 #ifdef DEBUG_PASSWORD
768         DEBUG(10,("mlocking memory: %p\n", memcredp->nt_hash));
769 #endif          
770         if ((mlock(memcredp->nt_hash, memcredp->len)) == -1) {
771                 DEBUG(0,("failed to mlock memory: %s (%d)\n", 
772                         strerror(errno), errno));
773                 SAFE_FREE(memcredp->nt_hash);
774                 return map_nt_error_from_unix(errno);
775         }
776
777 #ifdef DEBUG_PASSWORD
778         DEBUG(10,("mlocked memory: %p\n", memcredp->nt_hash));
779 #endif          
780
781         /* Create and store the password hashes. */
782         E_md4hash(pass, memcredp->nt_hash);
783         E_deshash(pass, memcredp->lm_hash);
784
785         if (pass) {
786                 memcredp->pass = (char *)memcredp->lm_hash + LM_HASH_LEN;
787                 memcpy(memcredp->pass, pass, memcredp->len - NT_HASH_LEN - LM_HASH_LEN);
788         }
789
790         return NT_STATUS_OK;
791 #endif
792 }
793
794 /***********************************************************
795  Destroy existing creds.
796 ***********************************************************/
797
798 static NTSTATUS delete_memory_creds(struct WINBINDD_MEMORY_CREDS *memcredp)
799 {
800 #if !defined(HAVE_MUNLOCK)
801         return NT_STATUS_OK;
802 #else
803         if (munlock(memcredp->nt_hash, memcredp->len) == -1) {
804                 DEBUG(0,("failed to munlock memory: %s (%d)\n", 
805                         strerror(errno), errno));
806                 return map_nt_error_from_unix(errno);
807         }
808         memset(memcredp->nt_hash, '\0', memcredp->len);
809         SAFE_FREE(memcredp->nt_hash);
810         memcredp->nt_hash = NULL;
811         memcredp->lm_hash = NULL;
812         memcredp->pass = NULL;
813         memcredp->len = 0;
814         return NT_STATUS_OK;
815 #endif
816 }
817
818 /***********************************************************
819  Replace the required creds with new ones (password change).
820 ***********************************************************/
821
822 static NTSTATUS winbindd_replace_memory_creds_internal(struct WINBINDD_MEMORY_CREDS *memcredp,
823                                                 const char *pass)
824 {
825         NTSTATUS status = delete_memory_creds(memcredp);
826         if (!NT_STATUS_IS_OK(status)) {
827                 return status;
828         }
829         return store_memory_creds(memcredp, pass);
830 }
831
832 /*************************************************************
833  Store credentials in memory in a list.
834 *************************************************************/
835
836 static NTSTATUS winbindd_add_memory_creds_internal(const char *username, uid_t uid, const char *pass)
837 {
838         /* Shortcut to ensure we don't store if no mlock. */
839 #if !defined(HAVE_MLOCK) || !defined(HAVE_MUNLOCK)
840         return NT_STATUS_OK;
841 #else
842         NTSTATUS status;
843         struct WINBINDD_MEMORY_CREDS *memcredp = find_memory_creds_by_name(username);
844
845         if (uid == (uid_t)-1) {
846                 DEBUG(0,("winbindd_add_memory_creds_internal: invalid uid for user %s.\n",
847                         username ));
848                 return NT_STATUS_INVALID_PARAMETER;
849         }
850
851         if (memcredp) {
852                 /* Already exists. Increment the reference count and replace stored creds. */
853                 if (uid != memcredp->uid) {
854                         DEBUG(0,("winbindd_add_memory_creds_internal: uid %u for user %s doesn't "
855                                 "match stored uid %u. Replacing.\n",
856                                 (unsigned int)uid, username, (unsigned int)memcredp->uid ));
857                         memcredp->uid = uid;
858                 }
859                 memcredp->ref_count++;
860                 DEBUG(10,("winbindd_add_memory_creds_internal: ref count for user %s is now %d\n",
861                         username, memcredp->ref_count ));
862                 return winbindd_replace_memory_creds_internal(memcredp, pass);
863         }
864
865         memcredp = TALLOC_ZERO_P(NULL, struct WINBINDD_MEMORY_CREDS);
866         if (!memcredp) {
867                 return NT_STATUS_NO_MEMORY;
868         }
869         memcredp->username = talloc_strdup(memcredp, username);
870         if (!memcredp->username) {
871                 talloc_destroy(memcredp);
872                 return NT_STATUS_NO_MEMORY;
873         }
874
875         status = store_memory_creds(memcredp, pass);
876         if (!NT_STATUS_IS_OK(status)) {
877                 talloc_destroy(memcredp);
878                 return status;
879         }
880
881         memcredp->uid = uid;
882         memcredp->ref_count = 1;
883         DLIST_ADD(memory_creds_list, memcredp);
884
885         DEBUG(10,("winbindd_add_memory_creds_internal: added entry for user %s\n",
886                 username ));
887
888         return NT_STATUS_OK;
889 #endif
890 }
891
892 /*************************************************************
893  Store users credentials in memory. If we also have a 
894  struct WINBINDD_CCACHE_ENTRY for this username with a
895  refresh timer, then store the plaintext of the password
896  and associate the new credentials with the struct WINBINDD_CCACHE_ENTRY.
897 *************************************************************/
898
899 NTSTATUS winbindd_add_memory_creds(const char *username, uid_t uid, const char *pass)
900 {
901         struct WINBINDD_CCACHE_ENTRY *entry = get_ccache_by_username(username);
902         NTSTATUS status;
903
904         status = winbindd_add_memory_creds_internal(username, uid, pass);
905         if (!NT_STATUS_IS_OK(status)) {
906                 return status;
907         }
908
909         if (entry) {
910                 struct WINBINDD_MEMORY_CREDS *memcredp = find_memory_creds_by_name(username);
911                 if (memcredp) {
912                         entry->cred_ptr = memcredp;
913                 }
914         }
915
916         return status;
917 }
918
919 /*************************************************************
920  Decrement the in-memory ref count - delete if zero.
921 *************************************************************/
922
923 NTSTATUS winbindd_delete_memory_creds(const char *username)
924 {
925         struct WINBINDD_MEMORY_CREDS *memcredp = find_memory_creds_by_name(username);
926         struct WINBINDD_CCACHE_ENTRY *entry = get_ccache_by_username(username);
927         NTSTATUS status = NT_STATUS_OK;
928
929         if (!memcredp) {
930                 DEBUG(10,("winbindd_delete_memory_creds: unknown user %s\n",
931                         username ));
932                 return NT_STATUS_OBJECT_NAME_NOT_FOUND;
933         }
934
935         if (memcredp->ref_count <= 0) {
936                 DEBUG(0,("winbindd_delete_memory_creds: logic error. ref count for user %s = %d\n",
937                         username, memcredp->ref_count));
938                 status = NT_STATUS_INTERNAL_DB_CORRUPTION;
939         }
940
941         memcredp->ref_count--;
942         if (memcredp->ref_count <= 0) {
943                 delete_memory_creds(memcredp);
944                 DLIST_REMOVE(memory_creds_list, memcredp);
945                 talloc_destroy(memcredp);
946                 DEBUG(10,("winbindd_delete_memory_creds: deleted entry for user %s\n",
947                         username));
948         } else {
949                 DEBUG(10,("winbindd_delete_memory_creds: entry for user %s ref_count now %d\n",
950                         username, memcredp->ref_count));
951         }
952
953         if (entry) {
954                 entry->cred_ptr = NULL; /* Ensure we have no dangling references to this. */
955         }
956         return status;
957 }
958
959 /***********************************************************
960  Replace the required creds with new ones (password change).
961 ***********************************************************/
962
963 NTSTATUS winbindd_replace_memory_creds(const char *username, const char *pass)
964 {
965         struct WINBINDD_MEMORY_CREDS *memcredp = find_memory_creds_by_name(username);
966
967         if (!memcredp) {
968                 DEBUG(10,("winbindd_replace_memory_creds: unknown user %s\n",
969                         username ));
970                 return NT_STATUS_OBJECT_NAME_NOT_FOUND;
971         }
972
973         DEBUG(10,("winbindd_replace_memory_creds: replaced creds for user %s\n",
974                 username ));
975
976         return winbindd_replace_memory_creds_internal(memcredp, pass);
977 }