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