source: branches/samba-3.2.x/source/winbindd/winbindd_cred_cache.c

Last change on this file was 232, checked in by Herwig Bauernfeind, 16 years ago

Update 3.2 branch to 3.2.8

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